Attractions is a modern and stylish UI kit for Svelte that provides beautiful, accessible components for building web applications. It offers a comprehensive set of form components with built-in styling and customization options. This article covers building complete forms with validation, error handling, and user feedback using Attractions form components in Svelte. This is part 10 of a series on using Attractions with Svelte.
This guide walks through creating a production-ready contact form with multiple input types, validation, error messages, and proper user experience using Attractions components.
Prerequisites
Before starting, ensure you have:
- A Svelte project (SvelteKit or standalone Svelte 3+)
- Node.js 14+ and npm/pnpm/yarn
- Basic understanding of Svelte reactivity and form handling
- Familiarity with JavaScript validation concepts
Installation
Install Attractions and its required dependencies using your preferred package manager:
npm install --save-dev attractions svelte-preprocess sass postcss autoprefixer
# or
pnpm add -D attractions svelte-preprocess sass postcss autoprefixer
# or
yarn add -D attractions svelte-preprocess sass postcss autoprefixer
The svelte-preprocess package is required for processing SCSS files, and sass is needed for compiling Attractions' styles.
Project Setup
1. Configure Svelte Preprocessor
Update your svelte.config.js to include the Attractions importer:
// svelte.config.js
import sveltePreprocess from 'svelte-preprocess';
import makeAttractionsImporter from 'attractions/importer.js';
const config = {
preprocess: sveltePreprocess({
scss: {
importer: makeAttractionsImporter(),
includePaths: ['src'],
},
postcss: true,
}),
// ... other config
};
export default config;
2. Import Attractions Styles
Create a global styles file or import in your main layout:
<!-- src/app.html or src/routes/+layout.svelte -->
<script>
import 'attractions/styles.css';
</script>
For standalone Svelte projects, import in your main entry file:
// main.js
import 'attractions/styles.css';
import App from './App.svelte';
const app = new App({
target: document.body
});
3. Optional: Customize Theme
Create a theme file to customize Attractions' default colors:
// src/_attractions-theme.scss
@use 'attractions/_variables' with (
$primary: #ff3e00,
$secondary: #1e90ff,
$error: #ff4444
);
Then update your svelte.config.js to use the theme:
// svelte.config.js
import sveltePreprocess from 'svelte-preprocess';
import makeAttractionsImporter from 'attractions/importer.js';
const config = {
preprocess: sveltePreprocess({
scss: {
importer: makeAttractionsImporter({
themeFile: 'src/_attractions-theme.scss',
}),
includePaths: ['src'],
},
postcss: true,
}),
};
export default config;
First Example / Basic Usage
Let's start with a simple form containing a text input with basic validation:
<!-- src/lib/SimpleForm.svelte -->
<script>
import { Input, Button } from 'attractions';
let email = '';
let emailError = '';
function validateEmail(value) {
if (!value) {
return 'Email is required';
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return 'Please enter a valid email address';
}
return '';
}
function handleEmailBlur() {
emailError = validateEmail(email);
}
function handleSubmit(event) {
event.preventDefault();
emailError = validateEmail(email);
if (!emailError) {
console.log('Form submitted:', { email });
// Reset form
email = '';
emailError = '';
}
}
</script>
<form on:submit={handleSubmit} class="form-container">
<Input
label="Email Address"
type="email"
bind:value={email}
error={emailError}
on:blur={handleEmailBlur}
placeholder="Enter your email"
/>
<Button type="submit" variant="filled">
Submit
</Button>
</form>
<style>
.form-container {
max-width: 400px;
margin: 2rem auto;
padding: 1.5rem;
}
</style>
This example demonstrates:
-
Input component: Attractions'
Inputwithbind:valuefor two-way binding -
Validation: Custom
validateEmailfunction that checks for required field and email format -
Error display: Using
errorprop to show validation errors -
Event handling:
on:blurevent to validate when user leaves the field - Form submission: Preventing default behavior and validating before submission
Understanding the Basics
Attractions components integrate seamlessly with Svelte's reactivity system. Key concepts:
Two-Way Binding
All form components support Svelte's bind:value directive:
<script>
import { Input } from 'attractions';
let username = '';
</script>
<Input
label="Username"
bind:value={username}
/>
Validation Pattern
The typical validation pattern in Svelte with Attractions:
<script>
import { Input } from 'attractions';
let value = '';
let error = '';
function validate(value) {
if (!value) return 'This field is required';
// Add more validation rules
return '';
}
function handleBlur() {
error = validate(value);
}
</script>
<Input
label="Field Label"
bind:value={value}
error={error}
on:blur={handleBlur}
/>
Multiple Input Types
Attractions supports various input types:
<script>
import { Input } from 'attractions';
let text = '';
let email = '';
let password = '';
let number = '';
</script>
<Input label="Text" type="text" bind:value={text} />
<Input label="Email" type="email" bind:value={email} />
<Input label="Password" type="password" bind:value={password} />
<Input label="Number" type="number" bind:value={number} />
Practical Example / Building Something Real
Let's build a complete contact form with multiple fields, validation, and proper error handling:
<!-- src/lib/ContactForm.svelte -->
<script>
import { Input, Button, Textarea } from 'attractions';
// Form data
let formData = {
name: '',
email: '',
phone: '',
subject: '',
message: ''
};
// Error states
let errors = {
name: '',
email: '',
phone: '',
subject: '',
message: ''
};
// Form state
let isSubmitting = false;
let submitSuccess = false;
// Validation functions
function validateName(value) {
if (!value.trim()) {
return 'Name is required';
}
if (value.trim().length < 2) {
return 'Name must be at least 2 characters';
}
return '';
}
function validateEmail(value) {
if (!value) {
return 'Email is required';
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return 'Please enter a valid email address';
}
return '';
}
function validatePhone(value) {
if (!value) {
return 'Phone is required';
}
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
if (!phoneRegex.test(value)) {
return 'Please enter a valid phone number';
}
return '';
}
function validateSubject(value) {
if (!value.trim()) {
return 'Subject is required';
}
if (value.trim().length < 5) {
return 'Subject must be at least 5 characters';
}
return '';
}
function validateMessage(value) {
if (!value.trim()) {
return 'Message is required';
}
if (value.trim().length < 10) {
return 'Message must be at least 10 characters';
}
return '';
}
// Handle field blur events
function handleNameBlur() {
errors.name = validateName(formData.name);
}
function handleEmailBlur() {
errors.email = validateEmail(formData.email);
}
function handlePhoneBlur() {
errors.phone = validatePhone(formData.phone);
}
function handleSubjectBlur() {
errors.subject = validateSubject(formData.subject);
}
function handleMessageBlur() {
errors.message = validateMessage(formData.message);
}
// Validate all fields
function validateAll() {
errors.name = validateName(formData.name);
errors.email = validateEmail(formData.email);
errors.phone = validatePhone(formData.phone);
errors.subject = validateSubject(formData.subject);
errors.message = validateMessage(formData.message);
// Check if form is valid
return !Object.values(errors).some(error => error !== '');
}
// Handle form submission
async function handleSubmit(event) {
event.preventDefault();
if (!validateAll()) {
return;
}
isSubmitting = true;
submitSuccess = false;
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
console.log('Form submitted:', formData);
// Reset form
formData = {
name: '',
email: '',
phone: '',
subject: '',
message: ''
};
errors = {
name: '',
email: '',
phone: '',
subject: '',
message: ''
};
submitSuccess = true;
// Hide success message after 3 seconds
setTimeout(() => {
submitSuccess = false;
}, 3000);
} catch (error) {
console.error('Submission error:', error);
} finally {
isSubmitting = false;
}
}
</script>
<div class="contact-form-container">
<h2>Contact Us</h2>
{#if submitSuccess}
<div class="success-message">
Thank you! Your message has been sent successfully.
</div>
{/if}
<form on:submit={handleSubmit} class="contact-form">
<Input
label="Full Name"
type="text"
bind:value={formData.name}
error={errors.name}
on:blur={handleNameBlur}
placeholder="John Doe"
required
/>
<Input
label="Email Address"
type="email"
bind:value={formData.email}
error={errors.email}
on:blur={handleEmailBlur}
placeholder="john@example.com"
required
/>
<Input
label="Phone Number"
type="tel"
bind:value={formData.phone}
error={errors.phone}
on:blur={handlePhoneBlur}
placeholder="+1 (555) 123-4567"
required
/>
<Input
label="Subject"
type="text"
bind:value={formData.subject}
error={errors.subject}
on:blur={handleSubjectBlur}
placeholder="What is this regarding?"
required
/>
<Textarea
label="Message"
bind:value={formData.message}
error={errors.message}
on:blur={handleMessageBlur}
placeholder="Enter your message here..."
rows="6"
required
/>
<div class="form-actions">
<Button
type="submit"
variant="filled"
disabled={isSubmitting}
>
{isSubmitting ? 'Sending...' : 'Send Message'}
</Button>
</div>
</form>
</div>
<style>
.contact-form-container {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
}
h2 {
margin-bottom: 1.5rem;
color: #333;
}
.success-message {
background-color: #4caf50;
color: white;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1.5rem;
text-align: center;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-actions {
margin-top: 0.5rem;
}
</style>
This complete example includes:
- Multiple input types: Text, email, tel inputs and textarea
- Comprehensive validation: Each field has specific validation rules
- Error handling: Errors are displayed per field
- Loading state: Button shows "Sending..." during submission
- Success feedback: Success message appears after submission
- Form reset: Form clears after successful submission
- Accessibility: All fields are properly labeled and required fields are marked
Common Issues / Troubleshooting
1. Styles Not Loading
Problem: Attractions components appear unstyled.
Solution: Ensure you've imported the styles correctly:
<script>
import 'attractions/styles.css';
</script>
Also verify that svelte-preprocess is configured in svelte.config.js.
2. SCSS Compilation Errors
Problem: Errors when compiling SCSS files.
Solution: Make sure sass is installed:
npm install -D sass
And verify the importer is set up correctly in svelte.config.js.
3. Validation Not Triggering
Problem: Validation doesn't run when expected.
Solution: Ensure you're handling the blur event:
<Input
bind:value={value}
error={error}
on:blur={handleBlur}
/>
Also validate on form submit to catch all errors.
4. Two-Way Binding Not Working
Problem: Input values don't update the bound variable.
Solution: Use bind:value instead of value:
<!-- Correct -->
<Input bind:value={myValue} />
<!-- Incorrect -->
<Input value={myValue} />
Next Steps
-
Explore More Components: Check out other Attractions components like
Select,Checkbox,Radio, andSwitchfor more complex forms - Customize Themes: Learn how to customize colors, spacing, and other design tokens using Sass variables
- Build Complex Forms: Combine multiple form components to create multi-step forms or dynamic form fields
- Accessibility: Review Attractions' accessibility features and ensure your forms meet WCAG guidelines
-
Form Libraries: Consider integrating with form libraries like
svelte-forms-libfor more advanced validation patterns
For more information, visit the Attractions documentation.
Summary
In this guide, we've covered creating forms with validation using Attractions in Svelte. You should now be able to build complete, production-ready forms with proper validation, error handling, and user feedback. The examples demonstrate how to integrate Attractions components with Svelte's reactivity system to create responsive and accessible forms.
Top comments (0)