Booking Band
When, how long and one clear booking action, on a tinted band or a centred card.
npx shadcn@latest add @vibebox/booking-cta-bandControls
<BookingCtaBand />About
A new section type for Launchpad sites that sell visits (clinics, salons, studios). booking-calendar-inline (Pro) puts a real calendar in the same place.
Use it when
- The site's main action is a booking.
Skip it when
- Bookings run through a booking module: the module's own block replaces this (Phase 7).
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the block with the shadcn CLI:
npx shadcn@latest add @vibebox/booking-cta-band - 3Use it:
import { BookingCtaBand } from "@/components/vibebox/booking-cta-band"; import { home } from "@/content/pages/home"; <BookingCtaBand content={home.booking} variant="band" id="book" />
Code
2 filescomponents/vibebox/booking-cta-band/index.tsx
/**
* VibeBox Motion · Booking Band
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
import { Actions, Container, Eyebrow, headingId, Icon, REVEAL, Section, SlotText, type BlockProps } from "@/lib/vibebox/block-kit";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";
/*
* The step before a booking: when, how long and one clear action, on a tinted band or a centred
* card. Details such as hours stay fill-ins until the client gives them.
*/
export type BookingCtaBandProps = BlockProps<Content, Variant>;
export function BookingCtaBand({ content, variant = "band", id, tone, className }: BookingCtaBandProps) {
const card = variant === "card";
const details = content.details.length > 0 && (
<dl className={cn("grid gap-x-8 gap-y-4", card ? "justify-center sm:grid-flow-col" : "sm:grid-cols-[repeat(auto-fit,minmax(10rem,1fr))]")}>
{content.details.map((detail, index) => (
<div key={index} className={cn("grid min-w-0 gap-1", card && "justify-items-center")}>
<SlotText as="dt" value={detail.label} path={`details.${index}.label`} className="text-small text-muted-foreground" />
<SlotText as="dd" value={detail.value} path={`details.${index}.value`} className="m-0 font-medium" />
</div>
))}
</dl>
);
return (
<Section slug="booking-cta-band" variant={variant} id={id} tone={tone} labelledBy={headingId(id)} spacing={false} className={cn("py-[clamp(48px,7vw,104px)]", className)}>
<Container>
<div
className={cn(
"relative isolate grid gap-8 overflow-hidden rounded-xl p-6 ring-1 ring-border sm:p-10 lg:p-14",
card ? "mx-auto max-w-3xl justify-items-center text-center" : "lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] lg:items-center lg:gap-16",
REVEAL,
)}
style={{ background: "linear-gradient(140deg, color-mix(in oklab, var(--spectrum-1) 12%, var(--card)), color-mix(in oklab, var(--spectrum-4) 9%, var(--card)))" }}
>
<span aria-hidden="true" className={cn("inline-grid size-12 place-items-center rounded-lg bg-background text-foreground ring-1 ring-border", !card && "hidden")}>
<Icon name="calendar" className="size-6" />
</span>
<div className={cn("grid gap-4", card && "justify-items-center")}>
<Eyebrow value={content.eyebrow} />
<SlotText as="h2" value={content.heading} path="heading" id={headingId(id)} className="font-display text-h2 font-semibold tracking-[-0.03em] text-balance" />
<SlotText as="p" value={content.lead} path="lead" className="max-w-[52ch] text-pretty text-muted-foreground" />
</div>
<div className={cn("grid gap-7", card && "justify-items-center")}>
{details}
<Actions primary={content.primaryAction} secondary={content.secondaryAction} size="lg" className={cn(card && "justify-center")} />
</div>
</div>
</Container>
</Section>
);
}components/vibebox/booking-cta-band/schema.ts
/**
* VibeBox Motion · Booking Band
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
import { sections, type z } from "@/lib/vibebox/content";
export const variants = ["band", "card"] as const;
export type Variant = (typeof variants)[number];
export const content = sections["booking-cta"];
export type Content = z.infer<typeof content>;Content
Typed slots from schema.ts. Facts (hours, prices, quotes, names) come only from the business; until then they render as fill-ins.
| Slot | Label | Type | Limit | Fact |
|---|---|---|---|---|
| eyebrow | Kicker | text | 32 chars | — |
| heading | Heading | text | 60 chars | — |
| lead | Intro | text | 160 chars | — |
| primaryAction | Main button | action | 24 chars | — |
| secondaryAction | Second button | action | 24 chars | — |
| details | Details | list | up to 3 | — |
| details[].label | Label | text | 24 chars | — |
| details[].value | Value | text | 60 chars | Can be a fill-in |
Variants
Banddefault
band
Heading left; details and actions right on desktop.
Card
card
A centred card with a calendar mark, details in a row.
Accessibility and motion
- Keyboard
- The two actions.
- Screen readers
- A heading, a line, a description list of details and the actions.
- Touch
- Nothing depends on hover.
- Reduced motion
- The band appears without its reveal.
Edge cases and limits
- Three long details wrap into rows.
Related

Booking Request
FreePick a service, a day and a time of day, leave a name and email: a real request the business answers.
Booking block · Server

CTA Band
FreeThe last nudge before the footer: one line and one action, as big type, a panel with art, or a quiet row.
Calls to action block · Server

Contact Strip
FreeA compact contact band: the heading, every channel on one line and one action.
Contact block · Server