# Appointment Booking Page

> A React appointment booking template for clinics, salons and coaches: next free times, a booking widget, services, team, hours and a map.

- Type: React template (Templates)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add appointment-booking-page`
- Docs: https://wingo-ui.com/templates/appointment-booking-page

## Interaction

A one-page site for a service business built around booking. The hero shows whether the practice is open right now (with when it closes or opens next, in the business time zone) and the next free appointments for the first service; picking one glides down to the booking widget with that time and service filled in, on the details step. Book an appointment, the header Book button and See all times glide to the widget. The widget is the full booking flow: service and practitioner, a calendar with the free times (closed days disabled, times in the practice's zone with a switch), the details form and a confirmation with an .ics download. Every service row has its length and price and a Book button that opens the widget on the times for that service; every practitioner card lists specialties and languages, and Book a visit preselects that person. Opening hours mark today and the Open now status; on phones days with the same hours share a row. Insurance and payment notes, an FAQ accordion with a contact card, and the address, phone, email and directions next to a map (below the contact card on phones). On phones a Book bar with the next free time slides up from the bottom while the hero, the widget and the footer are out of view. Keyboard: every slot, Book button and link is reachable with Tab and activates with Enter or Space; the widget keeps its own arrow-key navigation. Reduced motion keeps everything still.

## Installation

```bash
npx wingo-ui@latest add appointment-booking-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/appointment-booking-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/templates/appointment-booking-page.tsx`
- `components/templates/appointment-booking-page-data.ts`

npm dependencies: `npm i lucide-react motion`

Wingo UI dependencies (installed automatically):

- [Booking Widget](https://wingo-ui.com/components/booking-widget) ([Markdown](https://wingo-ui.com/components/booking-widget.md))
- [FAQ Section](https://wingo-ui.com/components/faq-section) ([Markdown](https://wingo-ui.com/components/faq-section.md))
- [Feature Grid](https://wingo-ui.com/components/feature-grid) ([Markdown](https://wingo-ui.com/components/feature-grid.md))
- [Marketing Section](https://wingo-ui.com/components/marketing-section) ([Markdown](https://wingo-ui.com/components/marketing-section.md))
- [Marketing Header](https://wingo-ui.com/components/marketing-header) ([Markdown](https://wingo-ui.com/components/marketing-header.md))
- [Marketing Footer](https://wingo-ui.com/components/marketing-footer) ([Markdown](https://wingo-ui.com/components/marketing-footer.md))
- [Location Map](https://wingo-ui.com/components/location-map) ([Markdown](https://wingo-ui.com/components/location-map.md))
- [Date Time Picker](https://wingo-ui.com/components/date-time-picker) ([Markdown](https://wingo-ui.com/components/date-time-picker.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/page.tsx
"use client"

import Link from "next/link"
import type { Booking, BookingSlotContext } from "@/components/blocks/booking-widget"
import { AppointmentBookingPage } from "@/components/templates/appointment-booking-page"

async function getTimeSlots(day: Date, { service, staff, timeZone }: BookingSlotContext) {
  const params = new URLSearchParams({ day: day.toDateString(), service: service.id, staff: staff?.id ?? "any", timeZone })
  const res = await fetch(`/api/availability?${params}`)
  return (await res.json()) as string[]
}

async function book(booking: Booking) {
  await fetch("/api/bookings", { method: "POST", body: JSON.stringify(booking) })
}

export default function HomeRoute() {
  return <AppointmentBookingPage linkComponent={Link} getTimeSlots={getTimeSlots} onBook={book} />
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `business?` | `AppointmentBusiness` | `"APPOINTMENT_BUSINESS"` | { name, tagline?, address, city, phone, email?, timeZone, hours, location?, directionsHref?, rating?, reviewCount?, hoursNote? }. hours is a list of { day (0 Sunday to 6 Saturday), open, close } wall times in timeZone; a day without an entry is closed, two entries make a lunch break. location { lat, lng } places the map pin. |
| `services?` | `AppointmentService[]` | `"APPOINTMENT_SERVICES"` | BookingService plus category and priceFrom: { id, name, duration (minutes), price?, description?, category?, priceFrom?, disabled? }. A price of 0 shows Free; priceFrom shows from $180. |
| `categories?` | `AppointmentServiceCategory[]` | `"APPOINTMENT_SERVICE_CATEGORIES"` | The service groups: { value, label, description? }. Services without a known category go under More services. |
| `staff?` | `AppointmentStaff[]` | `"APPOINTMENT_STAFF"` | BookingStaff plus specialties, bio and languages: { id, name, role?, avatar?, services?, specialties?, bio?, languages?, disabled? }. services lists the ids a person offers (empty: all). |
| `stats?` | `AppointmentStat[]` | `"APPOINTMENT_STATS"` | The numbers under the hero: { value, label }. [] hides them. |
| `visitNotes?` | `AppointmentVisitNote[]` | `"APPOINTMENT_VISIT_NOTES"` | The Before your visit card next to the hours: { title, description }. [] hides the card. |
| `paymentNotes?` | `FeatureGridItem[]` | `"APPOINTMENT_PAYMENT_NOTES"` | The insurance and payment cards: { icon, title, description }. |
| `faq?` | `FaqItem[]` | `"APPOINTMENT_FAQ"` | The questions: { question, answer, category? }. |
| `getTimeSlots?` | `(day: Date, context: BookingSlotContext) => (string \| DateTimeSlot)[] \| Promise<(string \| DateTimeSlot)[]>` |  | The free times of a day as "HH:mm" wall times in context.timeZone, as in BookingWidget; it feeds both the widget and the next free times in the hero. Pass a stable function. Default: createHoursTimeSlots over business.hours, a few times taken per day. |
| `slotStep?` | `number` | `30` | Minutes between the default free times (without getTimeSlots). |
| `onBook?` | `(booking: Booking) => void \| Promise<unknown>` |  | Called by Confirm booking with { service, staff, start, end, timeZone, details, bookedAt }. A promise keeps the button loading; a rejection shows the error. Default: a pretend 900 ms request. |
| `bookingWindow?` | `number` | `60` | How many days ahead can be booked. 0 means no limit. |
| `nextSlots?` | `number` | `3` | How many next free times the hero card lists (up to 6). 0 hides the card. |
| `nextSlotService?` | `string` | `""` | The service id the hero looks up free times for. Empty: the first service. |
| `bookingLayout?` | `"split" \| "stacked"` | `"split"` | The widget's layout: the summary column beside the steps, or a narrower card with the summary on top. |
| `hourCycle?` | `"auto" \| "h12" \| "h23"` | `"auto"` | 12 or 24 hour times in the hours, the free times and the widget; auto follows the locale. |
| `currency?` | `string` | `"USD"` | The ISO currency of the prices. |
| `locale?` | `string` | `""` | Formats days, times, prices and numbers. Empty: the UIProvider locale, else en-US. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The booking widget, the service and payment icon tiles and the open FAQ item. |
| `ctaTone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The solid Book buttons in the header, the hero and the phone Book bar. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"lg"` | Corners of the cards and the widget: 0, 12, 16 or 24px. |
| `background?` | `"plain" \| "soft" \| "glow" \| "aurora" \| "grid" \| "dots"` | `"glow"` | The page's main background (PageBackdrop). |
| `backgroundColor?` | `string` | `"oklch(0.62 0.14 275)"` | The tint of the main background and its decoration. |
| `bandBackground?` | `"muted" \| "tinted" \| "gradient" \| "dots"` | `"muted"` | The look of every second section (the booking band first). |
| `bandColor?` | `string` | `"oklch(0.62 0.14 275)"` | The tint of tinted and gradient bands; the backgroundColor when equal. |
| `showServices?` | `boolean` | `true` | The services grouped by category, each with its length, price and a Book button. |
| `showTeam?` | `boolean` | `true` | The practitioner cards with specialties, languages and Book a visit. |
| `showHours?` | `boolean` | `true` | The opening hours with today marked, the Open now status and the Before your visit card. |
| `showPayment?` | `boolean` | `true` | The insurance and payment notes (a FeatureGrid). |
| `showFaq?` | `boolean` | `true` | The FAQ (a FaqSection with a contact card). |
| `showContact?` | `boolean` | `true` | The address, phone, email, status and directions. |
| `showMap?` | `boolean` | `true` | The map next to the contact card (below it on phones). Needs business.location. |
| `stickyBook?` | `boolean` | `true` | Phones: a Book bar with the next free time pinned to the bottom while the hero, the widget and the footer are out of view. |
| `header?` | `boolean` | `true` | The sticky header with the section links, the phone number and Book. |
| `footer?` | `boolean` | `true` | The footer with Care, Visit and Practice links. |
| `headerProps?` | `Partial<MarketingHeaderProps>` |  | Anything for MarketingHeader (layout, variant, announcement...); wins over the page's own values. |
| `footerProps?` | `Partial<MarketingFooterProps>` |  | Anything for MarketingFooter (variant, columns, socials, newsletter...). |
| `bookingProps?` | `Partial<BookingWidgetProps>` |  | Anything for the embedded BookingWidget (requirePhone, showNotes, timeZones, rescheduleHref, labels...). |
| `paymentProps?` | `Partial<FeatureGridProps>` |  | Anything for the insurance and payment FeatureGrid (layout, columns, card...). |
| `faqProps?` | `Partial<FaqSectionProps>` |  | Anything for the FaqSection (layout, searchable, jsonLd...). |
| `now?` | `Date \| number` |  | What now means for the open status, today and the free times. Default: the clock. |
| `animateIn?` | `boolean` | `true` | The hero rises in once when the page mounts on the client; never during hydration, under reduced motion or in thumbnails. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for the header, footer and FAQ links (next/link in an app). |
| `labels?` | `Partial<AppointmentBookingPageLabels>` |  | Every string of the page (DEFAULT_APPOINTMENT_BOOKING_PAGE_LABELS holds the English defaults), including the formatters for durations, prices, the open status and the free times. |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<AppointmentBookingPageSlot, string>>` |  | Classes for header, hero, nextSlots, booking, widget, services, service, team, member, hours, visit, payment, faq, contact, map, sticky and footer. |

## Parts

### createHoursTimeSlots (function)

Builds a getTimeSlots function from opening hours: a free time every step minutes inside each opening, shown in the visitor's zone, with a few taken per day and person. The page's default; use it to prototype before your availability API exists.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `hours` | `AppointmentHoursRange[]` |  | The opening hours. |
| `timeZone` | `string` |  | The IANA zone the hours are in. |
| `options?` | `{ step?: number; seed?: string }` |  | Minutes between times (default 30) and a seed for the taken ones. |

### getBusinessOpenStatus (function)

Open or closed at a moment, with the closing time or the next opening: { open, today, closesAt?, opensAt?, opensIn?, opensDay? }. Read in the business time zone.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `hours` | `AppointmentHoursRange[]` |  | The opening hours. |
| `timeZone` | `string` |  | The IANA zone the hours are in. |
| `now` | `Date \| number` |  | The moment to check. |

### APPOINTMENT_BOOKING_PAGE_SAMPLE (constant)

All the sample content in one object.

### APPOINTMENT_BUSINESS (constant)

Brightside Dental on South Lamar in Austin, open Monday to Saturday in Central Time.

### APPOINTMENT_SERVICES (constant)

Eight dental services from a new patient exam to a same-day crown, with lengths and prices.

### APPOINTMENT_SERVICE_CATEGORIES (constant)

Checkups and cleanings, Cosmetic, Fillings and crowns, Urgent care.

### APPOINTMENT_STAFF (constant)

Three dentists and a hygienist with specialties, bios, languages and the services they offer.

### APPOINTMENT_STATS (constant)

The three numbers under the hero.

### APPOINTMENT_VISIT_NOTES (constant)

Four Before your visit notes: forms, cards, parking, access.

### APPOINTMENT_PAYMENT_NOTES (constant)

Six insurance and payment cards with icons.

### APPOINTMENT_FAQ (constant)

Six questions about new patients, insurance, cancellations, emergencies, nerves and payment plans.

### DEFAULT_APPOINTMENT_BOOKING_PAGE_LABELS (constant)

The English strings.

### AppointmentBookingPageProps (type)

Props of AppointmentBookingPage, including the attributes of the root element.

### AppointmentBookingPageLabels (type)

Every string of the page.

### AppointmentBookingPageRadius (type)

"none" | "sm" | "md" | "lg".

### AppointmentBookingPageSlot (type)

The keys of classNames.

### AppointmentBusiness (type)

The practice: name, address, phone, time zone, hours and map position.

### AppointmentHoursRange (type)

{ day, open, close }: one opening of a weekday, as "HH:mm".

### AppointmentWeekday (type)

0 (Sunday) to 6 (Saturday).

### AppointmentService (type)

A bookable service with its category.

### AppointmentServiceCategory (type)

{ value, label, description? }.

### AppointmentStaff (type)

A practitioner with specialties, bio and languages.

### AppointmentStat (type)

{ value, label }.

### AppointmentVisitNote (type)

{ title, description }.

### AppointmentSlot (type)

A free start the hero lists: { start, day, time }.

### AppointmentOpenStatus (type)

What getBusinessOpenStatus returns.

## Examples

- **A hair salon**: The same page for a salon in New York: other business, hours (closed Mondays), services, categories and stylists, with brand Book buttons.
- **Brand accents**: tone and ctaTone brand on an aurora page with tinted bands: the widget, icon tiles and Book buttons turn orange.
- **Closed right now**: now is a Sunday evening: the status reads Closed now, Opens tomorrow at 8:00 AM, and the first free times are on Monday.
- **A lean booking page**: Only the hero, the widget (stacked), the hours and the contact card, with square-ish corners and a dotted background.
- **A slow availability API**: getTimeSlots answers after 1.2s: the hero card shows skeleton rows until the first free times arrive.

## See also

- [Booking Widget](https://wingo-ui.com/components/booking-widget) ([Markdown](https://wingo-ui.com/components/booking-widget.md))
- [Demo Booking Page](https://wingo-ui.com/templates/demo-booking-page) ([Markdown](https://wingo-ui.com/templates/demo-booking-page.md))
- [Location Map](https://wingo-ui.com/components/location-map) ([Markdown](https://wingo-ui.com/components/location-map.md))
- [FAQ Section](https://wingo-ui.com/components/faq-section) ([Markdown](https://wingo-ui.com/components/faq-section.md))
- [Real Estate Page](https://wingo-ui.com/templates/real-estate-page) ([Markdown](https://wingo-ui.com/templates/real-estate-page.md))

---

Canonical: https://wingo-ui.com/templates/appointment-booking-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
