Minimal Service Picker

Book a visit

Choose what you need, then a time

Prices are published and cancellations more than a day ahead are free.

Soft blue gradient backdrop

This week

May
M12
T13
W14
T15
F16
S17
S18

Six slots open on Wednesday, from 08:00 to 16:30.

Initial consultation

A full hour with a clinician, ending in a written plan you take away.

Length
60 min
Price
£180
Next available
Wednesday 14 May
Choose a time

Covered by an insurer? The price shown is what they are billed.

About this block

Minimal Service PickerPRO

A quiet booking picker whose left column lists each service as a single hairline row carrying only its length and price, swapping a sticky panel that floats a live week strip above the chosen service's detail and its book action.

Booking26: Minimal Service Picker

A quiet booking picker whose left column lists each service as a single hairline row carrying only its length and price, swapping a sticky panel that floats a live week strip above the chosen service's detail and its book action.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/booking26?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/booking26?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/booking26.tsx, the calendar32 week-strip piece it floats on the tile (installed to components/beste/piece/calendar32.tsx), and the badge23 and button21 components it uses for the eyebrow and the book action.

Quick start

The installed file exports booking26Demo alongside the block: the exact props behind the preview above. Spread it to get a working picker in one line.

tsx
import { Booking26, booking26Demo } from "@/components/beste/block/booking26";

export default function BookingPage() {
  return <Booking26 {...booking26Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Booking26 } from "@/components/beste/block/booking26";
import { Calendar32 } from "@/components/beste/piece/calendar32";

export default function BookingPage() {
  return (
    <Booking26
      badge={{ label: "Book a visit" }}
      heading="Choose what you need, then a time"
      description="Prices are published and cancellations more than a day ahead are free."
      services={[
        {
          name: "Initial consultation",
          summary: "A full hour with a clinician, ending in a written plan.",
          duration: "60 min",
          price: "£180",
          next: "Wednesday 14 May",
        },
        {
          name: "Review appointment",
          summary: "Checking an existing plan and adjusting what is not working.",
          duration: "30 min",
          price: "£95",
          next: "Tomorrow, 09:00",
        },
      ]}
      labels={{ duration: "Length", price: "Price", next: "Next available" }}
      media={
        <Calendar32
          title="This week"
          month="May"
          days={[{ weekday: "W", date: "14", slots: 6 }]}
          selectedIndex={0}
          caption="Six slots open on Wednesday."
        />
      }
      image={{ src: "/backdrops/blue.jpg", alt: "Soft blue gradient backdrop" }}
      button={{ label: "Choose a time", href: "/booking/times" }}
      footnote="Covered by an insurer? The price shown is what they are billed."
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }Eyebrow above the hairline rule, rendered through Badge23
headingstringSection heading in the left column of the header
descriptionstringSupporting paragraph, right-aligned from md up
servicesService[][]Selectable services, the first one starts selected
labelsDetailLabelsThe three row labels in the detail table
mediaReactNodeLive asset on the tile, calendar32 in the demo
image{ src: string; alt: string }Backdrop behind the media tile
button{ label: string; href: string }Book action under the detail table
footnotestringSmall line under the action
classNamestringExtra classes for the outer section
ts
type ActionLink = {
  label: string;
  href: string;
};

type Service = {
  name: string;
  summary: string;
  duration: string;
  price: string;
  next: string;
};

type DetailLabels = {
  duration: string;
  price: string;
  next: string;
};

Behavior notes

  • The list rows deliberately show only name, length and price. Everything else about a service, its summary and next opening, appears in the panel once it is selected, which is what keeps a six-service list scannable in one glance.
  • Selection is one piece of local useState holding an index, starting at 0. There is no onChange prop and no controlled mode.
  • Selected rows are marked by a filled bg-muted surface plus a weight and colour change on the name, never by colour alone.
  • Rows carry px-4, so the selected fill extends past the text rather than cutting tight against it.
  • The tile and the detail table share one lg:sticky lg:top-24 lg:self-start column, so the whole panel travels together while the service list scrolls. The self-start is required for sticky to work inside a grid row.
  • labels drives all three detail rows, and the block reads the values straight off the selected service, so the table cannot drift out of sync with the list.
  • Prices use tabular-nums in both the list and the table, so the column stays optically aligned across different digit counts.
  • The rows are real button elements with aria-pressed and explicit cursor-pointer, since Tailwind v4 buttons default to cursor: default.

More Booking blocks

View all Booking
PRO

booking24

Service And Slot Picker

An interactive appointment picker: a hairline panel pairing a selectable service list with a scrollable day strip and a time slot grid that marks unavailable times, beside a full-height provider photo with a gradient name card, closing on a live summary bar and confirm action.

PRO

booking33

Session Picker

A booking chooser: a serif heading that settles in word by word and an intro, then a ruled list of session types on the left that swaps, with a soft blur, the muted detail card on the right showing a serif title, a description, a fact row and a sliding-marker pill.

FREE

booking4

Service Selector

Service selection grid with category filter tabs, service cards showing icon, name, description, duration, price, and selected state. Ideal for spa, salon, clinic, and fitness booking flows.

PRO

booking18

Cinema Seat Picker

Interactive seat selection grid for cinemas, theaters, and events. Row/column layout with an aisle gap, tier-based pricing (standard/premium/VIP), occupied seats, a live selection summary, and a legend. Includes screen indicator at the top.

PRO

booking1

Date & Time Picker

Split-panel booking interface with interactive monthly calendar on the left and time slot grid on the right. Features today highlight, disabled dates, selected state, morning/afternoon grouping, and booking summary footer.

PRO

booking27

Choose Your Clinician

A clinician picker whose hairline list of practitioners, each showing their next opening, swaps an image tile floating a live availability card alongside their focus, background, and sites.