Quaff is an extensive UI framework for Svelte featuring modern and elegant components that follow Material Design 3 principles. It provides a comprehensive set of pre-built, accessible components that help developers create beautiful, consistent user interfaces quickly. This article focuses on building interactive forms using Quaff's form components, including text inputs, buttons, checkboxes, and validation patterns.
This guide walks through creating production-ready forms using Quaff with Svelte, covering everything from basic form setup to advanced validation and user feedback. This is part 23 of a series on using Quaff with Svelte.
Prerequisites
Before starting, ensure you have:
- A Svelte project (SvelteKit recommended, but standalone Svelte works too)
- Node.js 18+ and npm/pnpm/yarn installed
- Basic familiarity with Svelte components, reactive statements, and event handling
- Understanding of HTML forms and form validation concepts
For this tutorial, we'll work with a SvelteKit project. If you're using standalone Svelte, the examples will work similarly, but you may need to adjust import paths.
Installation
Install Quaff using your preferred package manager:
npm install quaff
# or
pnpm add quaff
# or
yarn add quaff
The package includes all Quaff components and their TypeScript definitions. If you're using TypeScript, ensure your tsconfig.json has proper module resolution configured.
Project Setup
Quaff requires minimal setup. If you're using SvelteKit, you can start using components immediately. For styling, Quaff uses Material Design 3 tokens, so you may want to include the Material Design 3 CSS variables or use Quaff's built-in theming.
Create a basic layout file to include Quaff's styles (if required by your setup):
<!-- src/app.html or src/routes/+layout.svelte -->
<script>
// Quaff components work out of the box
// No additional configuration needed
</script>
If you need to customize the theme, you can add Material Design 3 CSS variables to your global styles:
/* src/app.css or global styles */
:root {
/* Material Design 3 color tokens */
--md-sys-color-primary: #6750a4;
--md-sys-color-on-primary: #ffffff;
/* Add other tokens as needed */
}
First Example: Basic Form
Let's start with a simple contact form using Quaff components:
<!-- src/routes/contact.svelte -->
<script>
import { TextField, Button } from 'quaff'
let formData = {
name: '',
email: '',
message: ''
}
function handleSubmit() {
console.log('Form submitted:', formData)
// Handle form submission
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<TextField
label="Name"
bind:value={formData.name}
required
/>
<TextField
label="Email"
type="email"
bind:value={formData.email}
required
/>
<TextField
label="Message"
bind:value={formData.message}
multiline
rows={4}
required
/>
<Button type="submit" variant="filled">
Submit
</Button>
</form>
This example demonstrates:
- TextField: Quaff's text input component with Material Design 3 styling
- Button: Quaff's button component with variant support
-
Two-way binding: Using
bind:valueto sync form state - Form handling: Standard Svelte form submission pattern
The components automatically include Material Design 3 styling, proper accessibility attributes, and focus management.
Understanding Form Components
Quaff provides several form-related components that work together:
TextField Component
The TextField component is the primary input component. It supports various input types and states:
<script>
import { TextField } from 'quaff'
let value = ''
let error = false
</script>
<!-- Basic text field -->
<TextField label="Username" bind:value={value} />
<!-- With helper text -->
<TextField
label="Email"
type="email"
helperText="Enter your email address"
bind:value={value}
/>
<!-- Error state -->
<TextField
label="Password"
type="password"
error={error}
errorText={error ? "Password is required" : ""}
bind:value={value}
/>
<!-- Disabled state -->
<TextField
label="Read-only field"
value="Cannot edit"
disabled
/>
Checkbox and Radio Components
For boolean choices and single selections:
<script>
import { Checkbox, Radio, RadioGroup } from 'quaff'
let termsAccepted = false
let selectedOption = 'option1'
</script>
<Checkbox
label="I agree to the terms and conditions"
bind:checked={termsAccepted}
/>
<RadioGroup bind:value={selectedOption}>
<Radio value="option1" label="Option 1" />
<Radio value="option2" label="Option 2" />
<Radio value="option3" label="Option 3" />
</RadioGroup>
Practical Example: Registration Form
Let's build a complete registration form with validation and error handling:
<!-- src/routes/register.svelte -->
<script>
import { TextField, Button, Checkbox } from 'quaff'
let formData = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
acceptTerms: false
}
let errors = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
acceptTerms: ''
}
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return re.test(email)
}
function validateForm() {
let isValid = true
errors = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
acceptTerms: ''
}
if (!formData.firstName.trim()) {
errors.firstName = 'First name is required'
isValid = false
}
if (!formData.lastName.trim()) {
errors.lastName = 'Last name is required'
isValid = false
}
if (!formData.email.trim()) {
errors.email = 'Email is required'
isValid = false
} else if (!validateEmail(formData.email)) {
errors.email = 'Please enter a valid email address'
isValid = false
}
if (!formData.password) {
errors.password = 'Password is required'
isValid = false
} else if (formData.password.length < 8) {
errors.password = 'Password must be at least 8 characters'
isValid = false
}
if (formData.password !== formData.confirmPassword) {
errors.confirmPassword = 'Passwords do not match'
isValid = false
}
if (!formData.acceptTerms) {
errors.acceptTerms = 'You must accept the terms and conditions'
isValid = false
}
return isValid
}
function handleSubmit() {
if (validateForm()) {
console.log('Registration data:', formData)
// Submit to your API
alert('Registration successful!')
}
}
</script>
<form on:submit|preventDefault={handleSubmit} class="form-container">
<div class="form-row">
<TextField
label="First Name"
bind:value={formData.firstName}
error={!!errors.firstName}
errorText={errors.firstName}
required
/>
<TextField
label="Last Name"
bind:value={formData.lastName}
error={!!errors.lastName}
errorText={errors.lastName}
required
/>
</div>
<TextField
label="Email"
type="email"
bind:value={formData.email}
error={!!errors.email}
errorText={errors.email}
helperText="We'll never share your email"
required
/>
<TextField
label="Password"
type="password"
bind:value={formData.password}
error={!!errors.password}
errorText={errors.password}
helperText="Must be at least 8 characters"
required
/>
<TextField
label="Confirm Password"
type="password"
bind:value={formData.confirmPassword}
error={!!errors.confirmPassword}
errorText={errors.confirmPassword}
required
/>
<Checkbox
label="I accept the terms and conditions"
bind:checked={formData.acceptTerms}
error={!!errors.acceptTerms}
errorText={errors.acceptTerms}
/>
<div class="button-group">
<Button type="submit" variant="filled" size="large">
Register
</Button>
<Button type="button" variant="outlined" on:click={() => {
formData = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
acceptTerms: false
}
errors = {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
acceptTerms: ''
}
}}>
Reset
</Button>
</div>
</form>
<style>
.form-container {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.button-group {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 1rem;
}
@media (max-width: 640px) {
.form-row {
grid-template-columns: 1fr;
}
.button-group {
flex-direction: column;
}
}
</style>
This example demonstrates:
- Complete form validation: Client-side validation with error messages
- Error state handling: Using Quaff's error props to show validation errors
- Form reset: Clearing form data and errors
- Responsive layout: Grid layout that adapts to screen size
- Material Design 3 styling: Automatic styling from Quaff components
Advanced: Form with Select and Date Picker
For more complex forms, Quaff provides additional components:
<script>
import { TextField, Button, Select, MenuItem, DatePicker } from 'quaff'
let formData = {
name: '',
country: '',
birthDate: null,
preferences: []
}
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
{ value: 'au', label: 'Australia' }
]
function handleSubmit() {
console.log('Form data:', formData)
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<TextField
label="Full Name"
bind:value={formData.name}
required
/>
<Select
label="Country"
bind:value={formData.country}
required
>
{#each countries as country}
<MenuItem value={country.value}>
{country.label}
</MenuItem>
{/each}
</Select>
<DatePicker
label="Date of Birth"
bind:value={formData.birthDate}
required
/>
<Button type="submit" variant="filled">
Submit
</Button>
</form>
Common Issues / Troubleshooting
Form values not updating
- Ensure you're using
bind:value(not justvalue) for two-way binding - Check that the variable is declared with
let, notconst - Verify the component name matches Quaff's API (case-sensitive)
Validation errors not showing
- Make sure you're passing both
error={true}anderrorText="message"props - Check that error state is reactive (use
$:for computed error states if needed) - Verify the error message is a non-empty string when error is true
Styling conflicts
- Quaff uses Material Design 3 CSS variables - ensure they're available globally
- Check for CSS specificity conflicts with your custom styles
- Use Quaff's built-in variant props instead of overriding styles directly
Components not rendering
- Verify Quaff is installed:
npm list quaff - Check import paths match your project structure
- Ensure you're using the correct component names (check Quaff documentation)
- For SvelteKit, components should work in both server and client components
Next Steps
Now that you understand how to build forms with Quaff, consider exploring:
- Form state management: Integrating with stores or form libraries like Felte or Svelte Forms Lib
- Server-side validation: Connecting client forms to API endpoints with validation
- Advanced components: Explore Quaff's other components like Data Tables, Navigation Drawers, and Dialogs
- Theming: Customizing Material Design 3 tokens to match your brand
- Accessibility: Testing forms with screen readers and keyboard navigation
- Form libraries integration: Using Quaff components with form validation libraries
For more information, visit the Quaff documentation
Summary
Quaff provides a powerful set of Material Design 3 components that make building forms in Svelte straightforward and accessible. You should now be able to create forms with text inputs, validation, error handling, and various input types. The components handle styling, accessibility, and user interaction patterns automatically, allowing you to focus on your application logic.
Top comments (0)