Forms That Feel Good to Use
A well-styled form builds trust and reduces friction. Users should know exactly what to do, see clear feedback, and feel confident about submitting. Let’s transform the booking form from functional to beautiful.
What You’ll Learn
- Style form inputs consistently
- Create clear visual hierarchy
- Design accessible form layouts
- Add visual feedback for states
Form Structure
Start with a clean HTML structure:
<!-- filename: src/lib/components/BookingForm.svelte -->
<form class="booking-form">
<div class="form-section">
<h2 class="section-title">Contact Information</h2>
<div class="form-row">
<div class="form-field">
<label for="name">Your Name</label>
<input type="text" id="name" bind:value={customerName} required />
</div>
<div class="form-field">
<label for="email">Email Address</label>
<input type="email" id="email" bind:value={customerEmail} required />
</div>
</div>
</div>
<div class="form-section">
<h2 class="section-title">Appointment Details</h2>
<!-- More fields... -->
</div>
<button type="submit" class="submit-btn">Request Booking</button>
</form> Base Form Styles
Set up the form container:
.booking-form {
max-width: 640px;
margin: 0 auto;
}
.form-section {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.section-title {
font-size: 1.125rem;
font-weight: 600;
color: #1f2937;
margin: 0 0 1.25rem 0;
padding-bottom: 0.75rem;
border-bottom: 1px solid #f3f4f6;
} This creates clear visual sections with subtle separation.
Input Styling
Make inputs feel premium:
.form-field {
margin-bottom: 1.25rem;
}
.form-field:last-child {
margin-bottom: 0;
}
.form-field label {
display: block;
font-size: 0.9375rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.5rem;
}
.form-field input,
.form-field select,
.form-field textarea {
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
line-height: 1.5;
color: #1f2937;
background: white;
border: 1px solid #d1d5db;
border-radius: 8px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
} Key details:
- Generous padding for comfortable tapping
- Subtle border that’s visible but not harsh
- Blue focus ring for clear indication
- Smooth transitions for polish
Placeholder Styling
Guide users without clutter:
.form-field input::placeholder,
.form-field textarea::placeholder {
color: #9ca3af;
}
/* Required field indicator */
.form-field label .required {
color: #ef4444;
margin-left: 0.25rem;
} <label for="name">
Your Name <span class="required">*</span>
</label> Two-Column Layout
For related fields like name/email:
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
/* Stack on mobile */
@media (max-width: 640px) {
.form-row {
grid-template-columns: 1fr;
}
} Select Dropdown Styling
Selects need extra attention:
.form-field select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
padding-right: 2.5rem;
cursor: pointer;
}
.form-field select:disabled {
background-color: #f9fafb;
cursor: not-allowed;
color: #9ca3af;
} The custom arrow replaces the default browser styling.
Textarea Styling
.form-field textarea {
min-height: 120px;
resize: vertical;
font-family: inherit;
} Radio Button Group
For service selection:
.service-options {
display: grid;
gap: 0.75rem;
}
.service-option {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem;
background: #f9fafb;
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
transition: all 0.15s;
}
.service-option:hover {
background: #f3f4f6;
}
.service-option.selected {
background: #eff6ff;
border-color: #3b82f6;
}
.service-option input[type="radio"] {
width: 1.25rem;
height: 1.25rem;
margin-top: 0.125rem;
accent-color: #3b82f6;
}
.service-option .service-info {
flex: 1;
}
.service-option .service-name {
font-weight: 600;
color: #1f2937;
}
.service-option .service-price {
color: #3b82f6;
font-weight: 600;
} {#each services as service (service.id)}
<label class="service-option" class:selected={selectedService === service.slug}>
<input
type="radio"
name="service"
value={service.slug}
bind:group={selectedService}
/>
<div class="service-info">
<span class="service-name">{service.name}</span>
<span class="service-price">${service.price}</span>
</div>
</label>
{/each} Checkbox Styling
For add-ons and terms:
.checkbox-field {
display: flex;
align-items: flex-start;
gap: 0.75rem;
}
.checkbox-field input[type="checkbox"] {
width: 1.25rem;
height: 1.25rem;
margin-top: 0.125rem;
accent-color: #3b82f6;
cursor: pointer;
}
.checkbox-field label {
cursor: pointer;
color: #4b5563;
line-height: 1.5;
} Submit Button
Make it stand out:
.submit-btn {
display: block;
width: 100%;
padding: 1rem 1.5rem;
font-size: 1.125rem;
font-weight: 600;
color: white;
background: #3b82f6;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.15s, transform 0.1s;
}
.submit-btn:hover {
background: #2563eb;
}
.submit-btn:active {
transform: scale(0.98);
}
.submit-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
transform: none;
} Error States
Show validation errors clearly:
.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error textarea {
border-color: #ef4444;
background: #fef2f2;
}
.form-field.has-error input:focus,
.form-field.has-error select:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
}
.error-message {
display: block;
margin-top: 0.5rem;
font-size: 0.875rem;
color: #ef4444;
} <div class="form-field" class:has-error={emailError}>
<label for="email">Email Address</label>
<input type="email" id="email" bind:value={customerEmail} />
{#if emailError}
<span class="error-message">{emailError}</span>
{/if}
</div> The Complete Form Styles
<!-- filename: src/lib/components/BookingForm.svelte -->
<style>
.booking-form {
max-width: 640px;
margin: 0 auto;
}
.form-section {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.section-title {
font-size: 1.125rem;
font-weight: 600;
color: #1f2937;
margin: 0 0 1.25rem 0;
padding-bottom: 0.75rem;
border-bottom: 1px solid #f3f4f6;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
@media (max-width: 640px) {
.form-row {
grid-template-columns: 1fr;
}
}
.form-field {
margin-bottom: 1.25rem;
}
.form-field:last-child {
margin-bottom: 0;
}
.form-field label {
display: block;
font-size: 0.9375rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.5rem;
}
.form-field input,
.form-field select,
.form-field textarea {
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
line-height: 1.5;
color: #1f2937;
background: white;
border: 1px solid #d1d5db;
border-radius: 8px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.form-field.has-error input {
border-color: #ef4444;
background: #fef2f2;
}
.error-message {
display: block;
margin-top: 0.5rem;
font-size: 0.875rem;
color: #ef4444;
}
.submit-btn {
display: block;
width: 100%;
padding: 1rem 1.5rem;
font-size: 1.125rem;
font-weight: 600;
color: white;
background: #3b82f6;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.15s, transform 0.1s;
}
.submit-btn:hover {
background: #2563eb;
}
.submit-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
</style> Summary
A well-styled form guides users through completion. Use consistent spacing, clear focus states, and helpful error messages. The visual hierarchy should make the next action obvious.
Key takeaways:
- Consistent input styling builds trust
- Focus states show where users are
- Error states provide clear feedback
- Large touch targets improve usability
Next Steps
Forms respond to user actions. Continue with Add Hover States to make interactions feel responsive and polished.