Skip to content
Motion
FreeIn reviewv1.0.0

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-band

Controls

Variant
<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

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the block with the shadcn CLI:
    npx shadcn@latest add @vibebox/booking-cta-band
  3. 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 files
components/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.

SlotLabelTypeLimitFact
eyebrowKickertext32 chars—
headingHeadingtext60 chars—
leadIntrotext160 chars—
primaryActionMain buttonaction24 chars—
secondaryActionSecond buttonaction24 chars—
detailsDetailslistup to 3—
details[].labelLabeltext24 chars—
details[].valueValuetext60 charsCan 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.
  • A booking request card with service chips, a preferred day and time, and name and email fields

    Pick a service, a day and a time of day, leave a name and email: a real request the business answers.

    Booking block · Server

  • Large type reading Ready when you are beside a line and two buttons

    The 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

  • A compact card with a heading, four contact channels and a Book a visit button

    A compact contact band: the heading, every channel on one line and one action.

    Contact block · Server

↑ ↓ to move↵ to openesc to close