Smelte is a UI framework built on top of Svelte and Tailwind CSS that adheres to Material Design specifications, providing a comprehensive set of pre-designed components for building beautiful and responsive web applications. This guide walks through building Material Design forms using Smelte with Svelte, covering everything from basic input fields to form validation and interactive components. This is part 18 of a series on using Smelte with Svelte.
Prerequisites
Before starting, ensure you have:
- Node.js version 16.x or higher installed
- SvelteKit project set up (or basic Svelte project with Rollup/Vite)
- Basic understanding of Svelte components and reactivity
- Familiarity with Tailwind CSS utility classes (helpful but not required)
Key Concepts to Understand:
- Material Design components: Pre-styled components following Google's Material Design guidelines that Smelte provides out of the box
- Form inputs: Text fields, checkboxes, selects, and other input elements with Material Design styling and ripple effects
- Form validation: Client-side validation using Svelte's reactive statements and bindings to provide user feedback
Installation
Install Smelte using your preferred package manager:
npm install smelte
Or with yarn:
yarn add smelte
Or with pnpm:
pnpm add smelte
This will add Smelte to your package.json dependencies.
Project Setup
1. Configure Rollup Plugin
If you're using Rollup (default for Svelte projects), add the Smelte Rollup plugin to your rollup.config.js. The plugin must be placed after the Svelte plugin but before the CSS plugin:
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import { smelte } from 'smelte/rollup-plugin-smelte';
import css from 'rollup-plugin-css-only';
const production = !process.env.ROLLUP_WATCH;
export default {
// ... other config
plugins: [
svelte({
// ... svelte options
}),
smelte({
purge: production,
output: 'public/global.css', // it defaults to static/global.css
postcss: [],
whitelist: [],
whitelistPatterns: [],
tailwind: {
colors: {
primary: '#b027b0',
secondary: '#009688',
error: '#f44336',
success: '#4caf50',
alert: '#ff9800',
blue: '#2196f3',
dark: '#212121'
},
darkMode: 'class',
},
}),
css({ output: 'bundle.css' }),
// ... other plugins
]
};
2. Import Tailwind CSS
In your main application file (typically src/main.js or src/App.svelte), import the Tailwind CSS utilities:
// src/main.js
import App from './App.svelte';
import 'smelte/src/tailwind.css';
const app = new App({
target: document.body
});
export default app;
3. Include Material Icons and Fonts
Add Material Icons and Roboto font to your HTML template (usually public/index.html or src/app.html):
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons&display=swap" rel="stylesheet" />
<title>Smelte Forms</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
First Example / Basic Usage
Let's create a simple form with a text field and a button to get started:
<!-- src/components/SimpleForm.svelte -->
<script>
import Textfield from 'smelte/src/components/Textfield';
import Button from 'smelte/src/components/Button';
let name = '';
let email = '';
function handleSubmit() {
console.log('Form submitted:', { name, email });
alert(`Hello ${name}! Your email is ${email}`);
}
</script>
<div class="container mx-auto p-8 max-w-md">
<h1 class="text-2xl font-bold mb-6">Simple Contact Form</h1>
<form on:submit|preventDefault={handleSubmit}>
<Textfield
label="Name"
bind:value={name}
class="mb-4"
/>
<Textfield
label="Email"
type="email"
bind:value={email}
class="mb-4"
/>
<Button type="submit" color="primary">
Submit
</Button>
</form>
</div>
What's happening here:
-
Textfieldis Smelte's Material Design text input component with built-in styling and animations -
Buttonprovides Material Design button styling with ripple effects - The
bind:valuedirective creates two-way data binding between the inputs and variables - The form prevents default submission and handles it with a custom function
Understanding the Basics
Component Import Pattern
Smelte uses tree-shaking, so you should import only the components you need:
<script>
// Import individual components
import Button from 'smelte/src/components/Button';
import Textfield from 'smelte/src/components/Textfield';
import Checkbox from 'smelte/src/components/Checkbox';
import Select from 'smelte/src/components/Select';
</script>
Material Design Styling
All Smelte components follow Material Design principles:
- Elevation: Components have subtle shadows for depth
- Ripple effects: Interactive elements show ripple animations on click
- Typography: Uses Roboto font family
- Colors: Customizable through Tailwind configuration
Two-Way Binding
Smelte components work seamlessly with Svelte's two-way binding:
<script>
import Textfield from 'smelte/src/components/Textfield';
let value = '';
</script>
<!-- The value updates reactively -->
<Textfield bind:value={value} />
<p>Current value: {value}</p>
Practical Example / Building Something Real
Let's build a complete registration form with validation, multiple input types, and proper error handling:
<!-- src/components/RegistrationForm.svelte -->
<script>
import Textfield from 'smelte/src/components/Textfield';
import Button from 'smelte/src/components/Button';
import Checkbox from 'smelte/src/components/Checkbox';
import Select from 'smelte/src/components/Select';
// Form data
let firstName = '';
let lastName = '';
let email = '';
let password = '';
let confirmPassword = '';
let country = '';
let acceptTerms = false;
// Validation errors
let errors = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
country: '',
acceptTerms: ''
};
// Validation functions
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
function validateForm() {
let isValid = true;
errors = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
country: '',
acceptTerms: ''
};
// First name validation
if (!firstName.trim()) {
errors.firstName = 'First name is required';
isValid = false;
}
// Last name validation
if (!lastName.trim()) {
errors.lastName = 'Last name is required';
isValid = false;
}
// Email validation
if (!email.trim()) {
errors.email = 'Email is required';
isValid = false;
} else if (!validateEmail(email)) {
errors.email = 'Please enter a valid email address';
isValid = false;
}
// Password validation
if (!password) {
errors.password = 'Password is required';
isValid = false;
} else if (password.length < 8) {
errors.password = 'Password must be at least 8 characters';
isValid = false;
}
// Confirm password validation
if (!confirmPassword) {
errors.confirmPassword = 'Please confirm your password';
isValid = false;
} else if (password !== confirmPassword) {
errors.confirmPassword = 'Passwords do not match';
isValid = false;
}
// Country validation
if (!country) {
errors.country = 'Please select a country';
isValid = false;
}
// Terms validation
if (!acceptTerms) {
errors.acceptTerms = 'You must accept the terms and conditions';
isValid = false;
}
return isValid;
}
function handleSubmit() {
if (validateForm()) {
console.log('Registration data:', {
firstName,
lastName,
email,
country,
acceptTerms
});
alert('Registration successful!');
// Reset form
firstName = '';
lastName = '';
email = '';
password = '';
confirmPassword = '';
country = '';
acceptTerms = false;
}
}
// Country options
const countries = [
{ value: '', label: 'Select a country' },
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
{ value: 'au', label: 'Australia' },
{ value: 'de', label: 'Germany' },
{ value: 'fr', label: 'France' }
];
</script>
<div class="container mx-auto p-8 max-w-2xl">
<h1 class="text-3xl font-bold mb-2">Create Your Account</h1>
<p class="text-gray-600 mb-8">Fill in the form below to get started</p>
<form on:submit|preventDefault={handleSubmit} class="space-y-6">
<!-- Name Fields Row -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Textfield
label="First Name"
bind:value={firstName}
error={errors.firstName}
required
/>
<Textfield
label="Last Name"
bind:value={lastName}
error={errors.lastName}
required
/>
</div>
<!-- Email Field -->
<Textfield
label="Email Address"
type="email"
bind:value={email}
error={errors.email}
required
helperText="We'll never share your email with anyone else"
/>
<!-- Password Fields Row -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Textfield
label="Password"
type="password"
bind:value={password}
error={errors.password}
required
helperText="Must be at least 8 characters"
/>
<Textfield
label="Confirm Password"
type="password"
bind:value={confirmPassword}
error={errors.confirmPassword}
required
/>
</div>
<!-- Country Select -->
<Select
label="Country"
bind:value={country}
error={errors.country}
required
options={countries}
/>
<!-- Terms Checkbox -->
<div class="pt-2">
<Checkbox
bind:checked={acceptTerms}
label="I accept the terms and conditions"
/>
{#if errors.acceptTerms}
<p class="text-error text-sm mt-1">{errors.acceptTerms}</p>
{/if}
</div>
<!-- Submit Button -->
<div class="flex justify-end pt-4">
<Button type="submit" color="primary" size="large">
Create Account
</Button>
</div>
</form>
</div>
Key features of this form:
- Validation: Real-time validation with error messages displayed below each field
- Multiple input types: Text fields, password fields, select dropdown, and checkbox
- Responsive layout: Grid layout that adapts to screen size
- Helper text: Additional guidance for users (e.g., password requirements)
- Required fields: Visual indicators for mandatory fields
- Form reset: Clears all fields after successful submission
Common Issues / Troubleshooting
Issue 1: Components not styling correctly
Problem: Smelte components appear unstyled or with default browser styling.
Solution:
- Ensure you've imported
smelte/src/tailwind.cssin your main file - Verify the Rollup plugin is configured correctly and placed after Svelte plugin
- Check that Material Icons and Roboto font are loaded in your HTML
Issue 2: Ripple effects not working
Problem: Buttons and interactive elements don't show ripple animations.
Solution:
- Make sure you're using the correct import path:
smelte/src/components/Button - Verify that the Smelte Rollup plugin is processing CSS correctly
- Check browser console for any JavaScript errors
Issue 3: Dark mode not applying
Problem: Dark mode classes aren't working as expected.
Solution:
- In
rollup.config.js, ensuredarkMode: 'class'is set in the Tailwind config - Add the
darkclass to your HTML element or a parent container - Verify your color palette includes dark mode variants
Issue 4: Form validation not triggering
Problem: Validation errors don't appear when expected.
Solution:
- Ensure you're using
bind:value(not justvalue) for two-way binding - Check that error messages are being set in your validation function
- Verify the
errorprop is being passed to the component correctly
Next Steps
Now that you've learned the basics of building forms with Smelte:
- Explore more components: Try out Dialogs, Snackbars, and Data Tables for more complex UIs
- Customize themes: Modify the color palette in your Rollup config to match your brand
- Add animations: Explore Smelte's animation utilities for enhanced user experience
- Build complex layouts: Combine forms with navigation, cards, and other Material Design components
- Check the documentation: Visit the Smelte GitHub repository for component API references and examples
For more articles in this series, explore other Smelte components and advanced patterns to build complete Material Design applications.
Summary
You've learned how to set up Smelte in a Svelte project and build Material Design forms with validation. You can now create forms with text fields, selects, checkboxes, and buttons, all styled according to Material Design guidelines with built-in ripple effects and animations.
Top comments (0)