In the second part of our series of building a fullstack site, we'll be adding in Tailwind and an amazing library that sits on top of it called Daisy UI. Tailwind has a mind-boggling level of flexibility, but sometimes you need to quickly create a site and worry about the specific design later. That's where Daisy UI comes in. Think of it like Bootstrap, but with all the goodies that comes with Tailwind. It has premade classes in place to simplify your development, so you can quickly scaffold a beautiful looking site without much effort.
Links we'll be using in this tutorial:
Step 1 - Install Tailwind
Even though we'll be installing Tailwind in this tutorial, we will really only be using Daisy UI for the setup.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init tailwind.config.cjs -p
mv postcss.config.js postcss.config.cjs
Now that Tailwind is installed, and our config files have been created, we can set up tailwind. Inside our tailwind.config.cjs
file, add this module.exports
function
module.exports = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: []
};
This setup requires a CSS file to be imported into your main __layout.svelte
file. This way all the tailwind components can be accessed in for all your pages. You can name this file anything you want, app.css
, global.css
, tailwind.css
, just as long as it's imported into the layout file, you're good to go.
I'm going to call mine global.css
and put it in the lib
folder next to my store.js
file.
├── source
│ ├── lib
│ │ ├── components
│ │ ├── data
│ │ ├── functions
│ │ ├── global.css
│ │ ├── store.js
│ ├── routes
│ │ ├── __layout.svelte
│ │ ├── index.svelte
│ └── app.html
Now we import the tailwind components into that file like so:
@tailwind base;
@tailwind components;
@tailwind utilities;
Then import it into our __layout.svelte
file. If you're following along with the Backendless part of the tutorial, you can include this import anywhere you want. I'm adding mine above the Backendless import statements.
// Import our global CSS
import "$lib/global.css";
And now we're done with Tailwind! You can test that it's working by launching the SvelteKit server npm run dev
and see that all of our previous styles are now missing. Tailwind is essentially resetting absolutely everything, so we have freedom to customize the site how we want.
Let's work on Daisy UI next!
Part 2 - Daisy UI
Next on our list is to install Daisy UI and configure it with Svelte. Since Daisy UI requires Tailwind, make sure all the previous steps are completed and your site looks similar to the previous screenshot.
Load up your terminal again and run:
npm i daisyui
Now open the tailwind.config.cjs
file and in the plugins
section, add in: require('daisyui')
like so:
module.exports = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: [
require('daisyui')
]
};
Make sure to save, and now load up your SvelteKit server with npm run dev
and see if any of your styles have changed! Your styles may look different from mine, but you can see new styles have been added to our project:
Before we go any further, I want to point out that Daisy UI has an amazing list of built-in themes. I'm going to set up the Forest theme for this site, but you can pick your whichever one you like best. You can see the full list here:
https://daisyui.com/docs/default-themes
The theme changer is in the top right corner of the page
To add a theme, you just modify the <html>
tag with a data-theme
attribute.
Time to test out some Daisy UI components. Let's style our form that we previously built on the __layout.svelte
file. First we'll want to add the Card styles to our form, then add the base input
styling, and then style up our button
.
Form Tag
Let's give this tag a class of card
and card-body
and then a width style of 400px, like so:
p.s. this is the same HTML form from Part 1 of this series
<form on:submit={handleLogin} class="card card-body" style="max-width: 400px">
<!-- The other html -->
</form>
Now we'll refactor our old form fields into Daisy UI ones. The HTML is a little more verbose, so this might be a great opportunity to create a form component that we can reuse. First, go to our components
folder inside of lib
, and create a new component called Input.svelte
. I won't go into too much detail about how Svelte components work, but this is how I structured mine:
<script>
export let label = "";
export let type = "";
export let id = "";
export let placeholder = "";
export let value = "";
const onInput = e => (value = e.target.value);
</script>
<div class="form-control">
<label class="label" for={id}>
<span class="label-text">{label}</span>
</label>
<input {type} {placeholder} {id} {value} on:input={onInput} class="input input-bordered">
</div>
Now we can import our new component back into the __layout.svelte
page by writing:
import Input from "$lib/components/Input.svelte";
And simplify all our form code into:
<form on:submit={handleLogin} class="card card-body" style="max-width: 400px">
<Input label="Email:" id="login-email" bind:value={loginData.email} type="email"/>
<Input label="Password:" id="login-password" bind:value={loginData.password} type="password"/>
<button type="submit" class="btn btn-primary">Log In</button>
</form>
Much cleaner!
Organization
So far, we've been working a lot in our main Layout file. Let's move this over to its own page called login.svelte
inside our routes
folder. And while we're at it, let's make a register.svelte
file too.
First thing we'll do inside the new Login page is to add our form and javascript code in (along with a few other classes and tags too).
login.svelte
<script>
import {user} from "$lib/store";
import Input from "$lib/components/Input.svelte";
let loginData = {
email: "",
password: "",
}
async function handleLogin(e) {
e.preventDefault();
// Log the user in. This returns a JSON object
let response = await Backendless.UserService.login(
loginData.email, loginData.password, true
);
// Save the updated user information to our svelte store
user.set(response);
}
</script>
<div class="container mx-auto">
<h1 class="text-4xl">Log In</h1>
<form on:submit={handleLogin} class="card card-body" style="max-width: 400px">
<Input label="Email:" id="login-email" bind:value={loginData.email} type="email"/>
<Input label="Password:" id="login-password" bind:value={loginData.password} type="password"/>
<button type="submit" class="btn btn-primary">Log In</button>
</form>
</div>
Now our code is nice and organized. We can do almost the same thing with the registration page. First create our new register.svelte
, and copy and paste all the same code from the Login page into this one.
Then, update the variable and function names to be registration related (such as changing let loginData
be let registerData
).
Then the registration function for Backendless is a little different. We need to create a new User
object. And that looks like this using our new variable names.
let user = new Backendless.User();
user.email = registerData.email;
user.password = registerData.password;
Now, since the registration is done through Javascript, we'll need to show the user that some kind of event has occurred. You could send them to a new registration confirmation page, either to let them know the account was created or maybe that they need to check their email, or you could display a message on page. For this example, we'll just use a svelte If/Else statement to control the UI. The whole register.svelte
page looks like this now:
<script>
import Input from "$lib/components/Input.svelte";
let pendingRegistration = true;
let registerData = {
email: "",
password: "",
}
async function handleRegister(e) {
e.preventDefault();
let user = new Backendless.User();
user.email = registerData.email;
user.password = registerData.password;
await Backendless.UserService.register(user)
pendingRegistration = !pendingRegistration;
}
</script>
<div class="container mx-auto">
<h1 class="text-5xl font-bold">Register</h1>
{#if pendingRegistration}
<form on:submit={handleRegister} class="card card-body" style="max-width: 400px">
<Input label="Email:" id="register-email" bind:value={registerData.email} type="email"/>
<Input label="Password:" id="register-password" bind:value={registerData.password} type="password"/>
<button type="submit" class="btn btn-primary">Register</button>
</form>
{:else}
<h2 class="text-5xl font-bold">Hooray!</h2>
{/if}
</div>
Once the registration has completed, the pendingRegistration
value is flipped so that the UI will update and hide the form. This way they don't try to submit the form again. And that's all it takes to set up a registration page.
Top comments (2)
cooooool
do you have the examples on github?