carbon-components-svelte is a Svelte component library that implements IBM's Carbon Design System, providing a collection of accessible and reusable UI components for building consistent interfaces. This article covers building complete forms with validation, error handling, and user feedback using carbon-components-svelte form components in Svelte. This is part 7 of a series on using carbon-components-svelte with Svelte.
This guide walks through creating a production-ready contact form with multiple input types, validation, error messages, and proper event handling using carbon-components-svelte components.
Prerequisites
Before starting, ensure you have:
- A Svelte project (SvelteKit or standalone Svelte 3+)
- Node.js 18+ and npm/pnpm/yarn
- Basic understanding of Svelte reactivity and form handling
- Familiarity with HTML form validation concepts
Installation
Install carbon-components-svelte using your preferred package manager:
npm install carbon-components-svelte
# or
pnpm add carbon-components-svelte
# or
yarn add carbon-components-svelte
The package includes all components and their styles.
Project Setup
After installation, you need to import Carbon styles into your application. In SvelteKit, add the import to your root layout or app.html:
<!-- src/app.html or src/routes/+layout.svelte -->
<style>
@import 'carbon-components-svelte/css/white.css';
</style>
For standalone Svelte projects, import in your main file:
<!-- App.svelte -->
<style>
@import 'carbon-components-svelte/css/white.css';
</style>
Carbon provides several themes: white.css (default), g10.css, g90.css, g100.css. Choose the theme that fits your design.
First Example / Basic Usage
Let's start with a simple form containing a text input with validation:
<!-- src/lib/ContactForm.svelte -->
<script>
import { TextInput, Button, Form } from 'carbon-components-svelte';
let email = '';
let emailError = '';
let isInvalid = false;
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);
isInvalid = !!emailError;
}
function handleSubmit(event) {
event.preventDefault();
emailError = validateEmail(email);
isInvalid = !!emailError;
if (!emailError) {
console.log('Form submitted:', { email });
// Handle form submission
email = '';
emailError = '';
isInvalid = false;
}
}
</script>
<Form on:submit={handleSubmit}>
<TextInput
id="email-input"
type="email"
labelText="Email Address"
bind:value={email}
invalid={isInvalid}
invalidText={emailError}
helperText="We'll never share your email with anyone else"
placeholder="Enter your email"
on:blur={handleEmailBlur}
/>
<Button type="submit" kind="primary">
Submit
</Button>
</Form>
This example demonstrates:
-
TextInput component:
TextInputfrom carbon-components-svelte withbind:valuefor two-way binding -
Validation:
validateEmailfunction checks for required field and email format -
Error display: Using
invalidandinvalidTextprops to show validation errors -
Helper text: Providing additional context with
helperText - Accessibility: All ARIA attributes are handled automatically by Carbon components
Understanding the Basics
carbon-components-svelte components integrate seamlessly with Svelte's reactivity system. Key concepts:
Two-Way Binding
All form components support Svelte's bind:value directive:
<script>
import { TextInput } from 'carbon-components-svelte';
let username = '';
</script>
<TextInput
id="username"
labelText="Username"
bind:value={username}
/>
Validation States
Components accept validation props:
-
invalid: Boolean to indicate invalid state -
invalidText: Error message to display -
helperText: Helper text shown when field is valid
Form Submission Handling
Handle form submission with standard Svelte event handlers:
<Form on:submit|preventDefault={handleSubmit}>
<!-- form fields -->
</Form>
Reactive Statements
Use Svelte reactive statements for automatic validation:
<script>
let password = '';
let invalid = false;
// Automatically updates when password changes
$: invalid = !/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,}$/.test(password);
</script>
Practical Example / Building Something Real
Let's build a complete contact form with multiple field types, validation, and proper error handling:
<!-- src/lib/ContactForm.svelte -->
<script>
import {
Form,
TextInput,
TextArea,
Button,
FormGroup,
RadioButtonGroup,
RadioButton,
Checkbox
} from 'carbon-components-svelte';
// Form state
let formData = {
name: '',
email: '',
phone: '',
subject: '',
message: '',
contactMethod: '',
newsletter: false
};
// Validation errors
let errors = {
name: '',
email: '',
phone: '',
subject: '',
message: '',
contactMethod: ''
};
// Field validity state
let invalidFields = {
name: false,
email: false,
phone: false,
subject: false,
message: false,
contactMethod: 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 && value.trim()) {
const phoneRegex = /^[\d\s\-\(\)]+$/;
if (!phoneRegex.test(value) || value.replace(/\D/g, '').length < 10) {
return 'Please enter a valid phone number';
}
}
return '';
}
function validateRequired(value, fieldName) {
if (!value || !value.trim()) {
return `${fieldName} is required`;
}
return '';
}
// Handle field blur events
function handleNameBlur() {
errors.name = validateName(formData.name);
invalidFields.name = !!errors.name;
}
function handleEmailBlur() {
errors.email = validateEmail(formData.email);
invalidFields.email = !!errors.email;
}
function handlePhoneBlur() {
errors.phone = validatePhone(formData.phone);
invalidFields.phone = !!errors.phone;
}
function handleSubjectBlur() {
errors.subject = validateRequired(formData.subject, 'Subject');
invalidFields.subject = !!errors.subject;
}
function handleMessageBlur() {
errors.message = validateRequired(formData.message, 'Message');
invalidFields.message = !!errors.message;
}
// Validate entire form
function validateForm() {
errors.name = validateName(formData.name);
errors.email = validateEmail(formData.email);
errors.phone = validatePhone(formData.phone);
errors.subject = validateRequired(formData.subject, 'Subject');
errors.message = validateRequired(formData.message, 'Message');
errors.contactMethod = validateRequired(formData.contactMethod, 'Preferred contact method');
invalidFields.name = !!errors.name;
invalidFields.email = !!errors.email;
invalidFields.phone = !!errors.phone;
invalidFields.subject = !!errors.subject;
invalidFields.message = !!errors.message;
invalidFields.contactMethod = !!errors.contactMethod;
return !Object.values(errors).some(error => error !== '');
}
// Handle form submission
function handleSubmit(event) {
event.preventDefault();
if (validateForm()) {
console.log('Form submitted:', formData);
// Here you would typically send data to your API
alert('Form submitted successfully!');
// Reset form
formData = {
name: '',
email: '',
phone: '',
subject: '',
message: '',
contactMethod: '',
newsletter: false
};
errors = {
name: '',
email: '',
phone: '',
subject: '',
message: '',
contactMethod: ''
};
invalidFields = {
name: false,
email: false,
phone: false,
subject: false,
message: false,
contactMethod: false
};
} else {
console.log('Form has validation errors:', errors);
}
}
</script>
<div class="form-container">
<h2>Contact Us</h2>
<Form on:submit={handleSubmit}>
<FormGroup legendText="Contact Information">
<!-- Name field -->
<TextInput
id="name-input"
type="text"
labelText="Full Name"
bind:value={formData.name}
invalid={invalidFields.name}
invalidText={errors.name}
helperText="Enter your full name"
placeholder="John Doe"
required
on:blur={handleNameBlur}
/>
<!-- Email field -->
<TextInput
id="email-input"
type="email"
labelText="Email Address"
bind:value={formData.email}
invalid={invalidFields.email}
invalidText={errors.email}
helperText="We'll use this to respond to your inquiry"
placeholder="john@example.com"
required
on:blur={handleEmailBlur}
/>
<!-- Phone field (optional) -->
<TextInput
id="phone-input"
type="tel"
labelText="Phone Number (Optional)"
bind:value={formData.phone}
invalid={invalidFields.phone}
invalidText={errors.phone}
helperText="Include country code if outside US"
placeholder="+1 (555) 123-4567"
on:blur={handlePhoneBlur}
/>
</FormGroup>
<FormGroup legendText="Message">
<!-- Subject field -->
<TextInput
id="subject-input"
type="text"
labelText="Subject"
bind:value={formData.subject}
invalid={invalidFields.subject}
invalidText={errors.subject}
placeholder="What is this regarding?"
required
on:blur={handleSubjectBlur}
/>
<!-- Message field -->
<TextArea
id="message-input"
labelText="Message"
bind:value={formData.message}
invalid={invalidFields.message}
invalidText={errors.message}
helperText="Please provide details about your inquiry"
placeholder="Enter your message..."
rows="5"
required
on:blur={handleMessageBlur}
/>
</FormGroup>
<FormGroup legendText="Additional">
<!-- Preferred contact method (Radio) -->
<RadioButtonGroup
id="contact-method"
labelText="Preferred Contact Method"
bind:selected={formData.contactMethod}
invalid={invalidFields.contactMethod}
invalidText={errors.contactMethod}
>
<RadioButton labelText="Email" value="email" id="contact-email" />
<RadioButton labelText="Phone" value="phone" id="contact-phone" />
<RadioButton labelText="Either" value="either" id="contact-either" />
</RadioButtonGroup>
<!-- Newsletter checkbox -->
<Checkbox
id="newsletter"
labelText="Subscribe to our newsletter for updates and tips"
bind:checked={formData.newsletter}
/>
</FormGroup>
<!-- Submit button -->
<Button type="submit" kind="primary" size="default">
Send Message
</Button>
</Form>
</div>
<style>
.form-container {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
}
h2 {
margin-bottom: 2rem;
font-size: 1.5rem;
font-weight: 600;
}
</style>
This complete form includes:
- Multiple input types: Text fields, email, phone, and textarea
- Real-time validation: Validates on blur and on submit
- Error messages: Displays specific error messages for each field
-
Radio buttons: Using
RadioButtonGroupfor contact method selection - Checkbox: Newsletter subscription option
- Accessibility: All fields are properly labeled and have ARIA attributes
- Form reset: Clears form after successful submission
Common Issues / Troubleshooting
Validation errors not showing
- Ensure the
invalidprop is set totruewhen there's an error - Check that
invalidTextcontains the error message string - Verify the validation function returns a non-empty string for errors
Two-way binding not working
- Make sure you're using
bind:value(not justvalue) - Check that the variable is declared with
let(notconst) - Verify the component supports binding (all carbon-components-svelte form components do)
CSS styles not applying
- Ensure you've imported the CSS:
@import 'carbon-components-svelte/css/white.css' - Check that the import is in the root layout or main file
- Verify the import path matches your package manager's structure
Form submission not preventing default behavior
- Add
|preventDefaultmodifier:on:submit|preventDefault={handleSubmit} - Or call
event.preventDefault()at the start of your handler function
Components not rendering correctly
- Ensure you're using the correct component names (e.g.,
TextInput, notInput) - Check that all necessary components are imported from
carbon-components-svelte - Verify the library version is compatible with your Svelte version
Next Steps
Now that you have a working form with validation, consider:
- Server-side validation: Implement validation on your backend API
- Async validation: Add email uniqueness checks or other async validations
- Form state management: Use Svelte stores for complex multi-step forms
- Custom validation rules: Create reusable validation utilities
- Accessibility enhancements: Test with screen readers and keyboard navigation
- Form libraries integration: Consider integrating with form libraries like Felte or Svelte Forms Lib
For more information, visit the carbon-components-svelte documentation and explore other components like Modal, Select, and DatePicker for more complex form scenarios.
Summary
You've learned how to build accessible, validated forms using carbon-components-svelte components in Svelte. The form includes multiple input types, real-time validation, error handling, and proper accessibility features. You can now create production-ready forms that work seamlessly with Svelte's reactivity system while maintaining full accessibility compliance.
Top comments (0)