Upcoming and past appointments list grouped by date, with service name, provider avatar, time, duration, status badges (confirmed/pending/cancelled), and reschedule/cancel action buttons.
A tabbed appointment manager rendering a bordered card whose tabs (e.g. Upcoming/Past) each hold date-grouped rows, every row showing a provider avatar, service name, provider name, a colored status badge, a time-and-duration line with optional location, and reschedule/cancel buttons, closed by a sticky footer "Book New Appointment" button.
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.
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
npx shadcn add "https://ui.beste.co/r/booking3?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/booking3?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/booking3.tsx, plus the avatar, badge, button, and tabs shadcn/ui primitives it uses for the provider images, status badges, action buttons, and tab switcher.
The installed file exports booking3Demo alongside the block: the exact props behind the preview above. Spread it to get a working appointment list in one line.
import { Booking3, booking3Demo } from "@/components/beste/block/booking3";
export default function AppointmentsPage() {
return <Booking3 {...booking3Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Booking3 } from "@/components/beste/block/booking3";
export default function AppointmentsPage() {
return (
<Booking3
badge={{ label: "My schedule", variant: "secondary" }}
heading="Your <strong>appointments</strong>"
description="Manage your upcoming and past bookings in one place."
tabs={[
{
label: "Upcoming",
groups: [
{
label: "Today",
appointments: [
{
service: "Deep Tissue Massage",
provider: { name: "Dr. Emily Carter", avatar: "/emily.jpg" },
time: "14:00",
duration: "60 min",
location: "Serenity Spa",
status: "confirmed",
},
],
},
],
},
]}
labels={{
statusConfirmed: "Confirmed",
rescheduleButton: "Reschedule",
cancelButton: "Cancel",
footerButton: "Book New Appointment",
}}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
badge | { label: string; variant?: "default" | "secondary" | "outline" } | – | Optional eyebrow badge above the heading |
heading | string | – | Section heading, supports inline <strong> |
description | string | – | Optional supporting line under the heading |
tabs | Booking3Tab[] | [] | Tab entries, each holding date-grouped appointments |
labels | Booking3Labels | {} | Status label and button text overrides |
className | string | – | Extra classes for the outer <section> |
type AppointmentStatus = "confirmed" | "pending" | "cancelled";
type Appointment = {
service: string;
provider: { name: string; avatar?: string };
time: string;
duration: string;
location?: string;
status: AppointmentStatus;
};
type DateGroup = {
label: string;
appointments: Appointment[];
};
type Booking3Tab = {
label: string;
groups: DateGroup[];
};
type Booking3Labels = {
statusConfirmed?: string;
statusPending?: string;
statusCancelled?: string;
rescheduleButton?: string;
cancelButton?: string;
footerButton?: string;
};Tabs component with defaultValue set to tabs[0]?.label; each tab's label doubles as its value, so two tabs sharing a label would collide and neither the active tab nor selection is exposed via any callback.heading is injected with dangerouslySetInnerHTML, and the [&>strong] styles bold plus tint any inline <strong> in the primary color; only pass trusted markup.apt.status: confirmed is emerald and prefixed with a Check icon, pending is amber, and cancelled is rose. The visible badge text comes from the matching labels.status* value, so an unset label renders an empty badge.cancelled appointment dims its whole row to opacity-60, strikes through the service name, and hides the reschedule/cancel button pair entirely; only non-cancelled rows show those two buttons.onClick handler at all, so wiring real actions is left to the integrator.max-w-lg and the card body has no internal scroll limit, so long appointment lists grow the page.location is optional per appointment: when absent, the MapPin line is omitted and only the Clock time-and-duration span shows.booking2
Booking confirmation card with service details, date/time, provider info with avatar, price breakdown with subtotal/tax/discount/total, promo code input, cancellation policy, and confirm CTA.
booking4
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.
booking5
Staff/provider selection cards with avatar, name, specialty, star rating, review count, availability badge, experience years, and selected state. Grid layout with "Any Provider" option.
booking15
Post-booking success screen with animated check icon, confirmation number, booking summary (service, date/time, location, total), scannable QR code ticket, action buttons (calendar, receipt, directions), and email confirmation note.
booking9
Event ticket selection with event banner, date/venue info, ticket tier cards (General/VIP/Backstage) with perks list, quantity selector, and order total. Concert and conference ticket flow.
booking21
Gym or studio weekly schedule with day tabs, class list per day (instructor avatar, duration, spots remaining, difficulty), and quick reserve action. Tap a day to see its classes, tap a class to book a spot.