The Heart of BookIt

Every booking app needs a form — it’s the primary way customers interact with your service. They enter their details, select what they want, choose when they want it, and submit their booking. Without this form, BookIt would just be a static brochure.

In this lesson, you’ll build this form as a reusable component. Components are one of Svelte’s most powerful features. Instead of copy-pasting the same form HTML across multiple pages, you’ll write it once and use it anywhere.

What is a Component?

Think of a component as a custom HTML tag that you define. Just like you can write <button> or <input> and they work anywhere, you can create a <BookingForm> tag and use it wherever you need a booking form.

Components encapsulate:

  • Structure (HTML markup)
  • Behavior (JavaScript logic)
  • Styling (CSS)

This makes your codebase modular, maintainable, and easier to reason about.


What You’ll Learn

  • Create a new component file
  • Build basic form structure with HTML
  • Import and use the component in a page

Create the Component File

Components in Svelte are simply .svelte files — that’s it. Any file with a .svelte extension is a component that can be imported and reused throughout your application.

The src/lib Folder Convention

SvelteKit has a special folder called src/lib for shared code. This is where you put:

  • Reusable components (like our booking form)
  • Utility functions (date formatters, validators, etc.)
  • Type definitions (TypeScript interfaces)
  • Constants and configuration

Anything you put in src/lib can be imported from anywhere in your app using the $lib alias (more on this soon).

Creating the Component

Lets create our first Svelte component:

  1. Create the src/lib/components/ directory if it doesn’t exist
  2. Inside it, create a new file called BookingForm.svelte
src/
├── lib/                          ← Shared code
│   └── components/               ← Reusable UI components
│       └── BookingForm.svelte    ← Create this file
└── routes/                       ← Pages and endpoints
    └── ...

Why components/? While you could put BookingForm.svelte directly in src/lib, creating a components/ subfolder keeps things organized. As your app grows, you might have dozens of components, and having them in their own folder prevents clutter.

Naming convention: Component files are typically named in PascalCase (like BookingForm.svelte), matching the way you’ll use them: <BookingForm />. This makes it clear at a glance that it’s a component, not a utility function or constant.


Build the Form Structure

Now let’s build the actual form. We’ll start with plain, semantic HTML — no JavaScript, no reactivity, just a well-structured form. Getting the HTML foundation right is crucial because it affects accessibility, user experience, and how easy it will be to add interactivity later.

What Makes a Good Form?

Before we write code, let’s think about what a booking form needs:

  1. Clear labels — Users should know what each field is for
  2. Appropriate input typestype="email" for email, type="date" for dates, etc.
  3. Helpful placeholders — Examples of what to enter
  4. Logical grouping — Related fields grouped together
  5. Accessible markup — Labels associated with inputs for screen readers

Building the HTML

Add this HTML structure to your BookingForm.svelte file:

<!-- filename: src/lib/components/BookingForm.svelte -->
<form>
	<h2>Book a Service</h2>

	<div class="field">
		<label for="name">Your Name</label>
		<input type="text" id="name" name="name" placeholder="Jane Smith" />
	</div>

	<div class="field">
		<label for="email">Email Address</label>
		<input type="email" id="email" name="email" placeholder="jane@example.com" />
	</div>

	<div class="field">
		<label for="date">Preferred Date</label>
		<input type="date" id="date" name="date" />
	</div>

	<div class="field">
		<label for="time">Preferred Time</label>
		<select id="time" name="time">
			<option value="">Select a time...</option>
			<option value="09:00">9:00 AM</option>
			<option value="10:00">10:00 AM</option>
			<option value="11:00">11:00 AM</option>
			<option value="13:00">1:00 PM</option>
			<option value="14:00">2:00 PM</option>
			<option value="15:00">3:00 PM</option>
		</select>
	</div>

	<div class="field">
		<label for="notes">Additional Notes</label>
		<textarea id="notes" name="notes" rows="3" placeholder="Any special requests?"></textarea>
	</div>

	<button type="submit">Request Booking</button>
</form>

Understanding the Structure

Let’s break down what each part does:

The form wrapper:

<form>

The <form> element wraps all inputs. When users press Enter or click the submit button, the form triggers a submit event. We’ll handle that in later lessons.

Field wrapper pattern:

<div class="field">
	<label for="name">Your Name</label>
	<input type="text" id="name" name="name" placeholder="Jane Smith" />
</div>

Each field is wrapped in a <div class="field">. This:

  • Groups the label and input together
  • Makes styling easier (we can target .field in CSS)
  • Creates semantic sections in the form

The for and id connection:

  • The label’s for="name" attribute matches the input’s id="name"
  • This associates the label with the input
  • Clicking the label focuses the input
  • Screen readers announce the label when the input is focused

Input types matter:

  • type="text" — Plain text input for names
  • type="email" — Validates email format, shows email keyboard on mobile
  • type="date" — Shows a date picker in modern browsers
  • <select> — Dropdown for choosing from preset options
  • <textarea> — Multi-line text for notes

Why this is plain HTML: Notice there’s no $state, no reactive variables, no event handlers. This is intentional. We’re building the structure first, then adding behavior. This is good practice — get the HTML right, then make it interactive.


Use the Component

Now that we’ve created the form component, let’s use it on the service detail page. This is where users will see service information and be able to book that specific service.

How Component Imports Work

In Svelte, using a component is a two-step process:

  1. Import the component in your <script> tag
  2. Use it in your markup like an HTML tag

The component name becomes a custom HTML tag. If you import BookingForm, you use it as <BookingForm />. The self-closing slash (/>) is important — components without children should be self-closing.

Adding to the Service Page

Update src/routes/services/[slug]/+page.svelte:

<!-- filename: src/routes/services/[slug]/+page.svelte -->
<script>
	import BookingForm from '$lib/components/BookingForm.svelte'

	let { data } = $props()
</script>

<nav>
	<a href="/services">← Back to Services</a>
</nav>

{#if data.service}
	<h1>{data.service.name}</h1>
	<p>{data.service.description}</p>

	<dl>
		<dt>Price</dt>
		<dd>${data.service.price}</dd>

		<dt>Duration</dt>
		<dd>{data.service.duration} minutes</dd>
	</dl>

	<BookingForm />
{:else}
	<h1>Service Not Found</h1>
	<p>We couldn't find that service.</p>
	<a href="/services">Browse all services</a>
{/if}

What Changed?

Two small but crucial additions:

1. The import statement:

import BookingForm from '$lib/components/BookingForm.svelte';

This tells Svelte: “I want to use the BookingForm component from the file at src/lib/components/BookingForm.svelte.”

  • import BookingForm — The name you’ll use in your markup (can be anything, but should match the filename)
  • from '$lib/components/BookingForm.svelte' — Where to find the component
  • The .svelte extension is required (unlike some frameworks where you can omit it)

2. Using the component:

<BookingForm />

This renders the component at this location in your page. It’s like placing the entire contents of BookingForm.svelte right here, but in a clean, reusable way.

The / at the end means this is a self-closing tag (no children). For components with children, you’d write <BookingForm>content</BookingForm>, but our form doesn’t need that.

Where in the Page?

We placed <BookingForm /> after the service details:

<h1>{data.service.name}</h1>
<p>{data.service.description}</p>
<dl>...</dl>
<BookingForm /> ← Added here

This creates a logical flow: users read about the service, then immediately see how to book it.


The $lib Alias

Notice we imported from $lib/components/BookingForm.svelte, not a relative path like ../../../lib/components/BookingForm.svelte.

SvelteKit provides the $lib alias that always points to src/lib. This makes imports cleaner and avoids the ../../../ mess as your project grows.

<!-- ❌ Fragile — breaks if you move the file -->
import BookingForm from '../../../lib/components/BookingForm.svelte';

<!-- ✅ Stable — always works from anywhere -->
import BookingForm from '$lib/components/BookingForm.svelte';

Test the Form

Let’s verify that everything works. Make sure your dev server is running (npm run dev or pnpm dev) and visit a service detail page.

Testing Steps

  1. Navigate to a service:

    • Go to http://localhost:5173/services
    • Click any service (e.g., “Lawn Mowing”)
    • Or directly visit http://localhost:5173/services/lawn-mowing
  2. Check the layout: You should see (from top to bottom):

    • Service name as a heading
    • Service description
    • Price and duration information
    • The booking form with all fields
  3. Try interacting with the form:

    • Type in the “Your Name” field — text appears
    • Type in the “Email Address” field — text appears
    • Click the date picker — calendar opens
    • Select a time from the dropdown — option changes
    • Type in the notes field — text appears
  4. Click the submit button:

    • The page refreshes
    • All your typed data disappears
    • Nothing is saved or sent anywhere

What’s Working and What’s Not

✅ Working:

  • The form displays correctly
  • All fields are visible and styled
  • You can type and select values
  • The HTML structure is sound

❌ Not working yet:

  • Typed values aren’t tracked in JavaScript
  • Nothing happens on form submission
  • No validation
  • No preview of the booking
  • Data isn’t saved anywhere

Why It Feels “Broken”

The form might feel broken because it doesn’t do anything yet. This is expected! We’ve built the structure (the HTML skeleton), but we haven’t added the behavior (the JavaScript that tracks and responds to user input).

That’s what we’ll fix in the next lesson by adding $state to track form fields.


Why Components?

You might be wondering: “Why create a separate file? Why not just put the form HTML directly in the service page?”

This is a great question, especially if you’re new to component-based development. Let’s explore the benefits.

1. Reusability: Write Once, Use Everywhere

Imagine you decide to also show the booking form on:

  • A dedicated /book page
  • A modal popup when users click “Quick Book”
  • An admin page for creating test bookings

Without a component, you’d copy-paste the form HTML to all these places. Now you have four copies of the same form. If you need to add a new field, you change it four times. If you find a bug, you fix it four times. This is a maintenance nightmare.

With a component:

<!-- Any page -->
<script>
	import BookingForm from '$lib/components/BookingForm.svelte'
</script>

<BookingForm />

One component, infinite uses. Change it once, updates everywhere.

2. Organization: Separation of Concerns

Pages should focus on page-level concerns:

  • What data to load
  • Page layout and structure
  • Which components to show

They shouldn’t be cluttered with the details of every form, modal, or complex widget. Components let you separate what (the page) from how (the component implementation).

Compare:

<!-- ❌ Page does everything —  hard to scan -->
<h1>Service Details</h1>
<!-- 150 lines of form HTML -->
<!-- 50 lines of form logic -->
<!-- 80 lines of form styling -->

Versus:

<!-- ✅ Page delegates to component — easy to understand -->
<h1>Service Details</h1>
<BookingForm />

The second version is easier to understand at a glance.

3. Isolation: Prevent Bugs

When form logic lives in the same file as page logic, they can interfere with each other. Variable names might conflict. Event handlers might accidentally affect the wrong elements.

Components create boundaries. The form’s logic stays in BookingForm.svelte. The page’s logic stays in +page.svelte. They communicate through well-defined interfaces (props and events), not by sharing global state.

4. Testability

Need to test the booking form? If it’s a component, you can:

<script>
	import BookingForm from '$lib/components/BookingForm.svelte'
	import { mount } from 'svelte'

	// Test the form in isolation
	const form = mount(BookingForm, { target: document.body })
</script>

If it’s embedded in a page, you’d have to test the entire page, service data loading, routing, and everything else just to test the form.

Real-World Parallel

Think about cooking. You could write a recipe as one giant paragraph describing everything from shopping to plating. Or you could break it into sections: ingredients, preparation, cooking, serving.

Components are like those sections — they break complex UIs into manageable, named pieces.

When to Create Components

As a rule of thumb, create a component when:

  • You’ll use it in multiple places
  • It’s complex enough to clutter the parent file
  • It represents a distinct UI concept (like a form, card, modal)
  • You want to test it independently

For now, just know that component thinking is fundamental to modern web development. We’ll explore this deeply in Module 7: Components.


Your Project Structure

src/
├── lib/
│   └── components/
│       └── BookingForm.svelte    ← New component
└── routes/
    └── services/
        └── [slug]/
            ├── +page.js
            └── +page.svelte      ← Uses BookingForm

Common Mistakes

Wrong Import Path

<!-- ❌ Missing file extension -->
import BookingForm from '$lib/components/BookingForm';

<!-- ✅ Include .svelte extension -->
import BookingForm from '$lib/components/BookingForm.svelte';

Svelte component imports require the .svelte extension.

Component Outside src/lib

You can technically put components anywhere, but src/lib is conventional. If you create components in src/components instead, the $lib alias won’t work — you’d need relative imports.


Summary

You’ve created BookIt’s booking form as a reusable component. It lives in src/lib/components and can be imported anywhere using the $lib alias. The form renders but isn’t interactive yet.

Key takeaways:

  • Components are .svelte files you import and use like HTML tags
  • Place reusable components in src/lib/components
  • Use $lib alias for clean imports

Next Steps

The form displays but doesn’t track what users type. Continue with Track Form Fields with $state to make the form reactive.