DEV Community

Ethan Walker
Ethan Walker

Posted on

Building Forms with Validation in Attractions and Svelte

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Input component: Attractions' Input with bind:value for two-way binding
  • Validation: Custom validateEmail function that checks for required field and email format
  • Error display: Using error prop to show validation errors
  • Event handling: on:blur event 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}
/>
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

Next Steps

  • Explore More Components: Check out other Attractions components like Select, Checkbox, Radio, and Switch for 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-lib for 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)