Vehicle rental flow with pickup and return location fields, date range picker, car class grid (compact/suv/luxury) with image and specs, insurance tier selector, optional add-ons, and live price breakdown.
A single-card vehicle rental configurator that stacks pickup and return location selects (with a "same location" checkbox that mirrors and disables the return field), a range calendar in a popover, a selectable list of car-class cards showing image, seat and bag counts, transmission, fuel, and per-day price, a tabbed insurance tier selector, a checkbox grid of add-ons, and a live subtotal-plus-total breakdown that recomputes from the selected day count on every change.
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/booking20?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/booking20?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/booking20.tsx, plus the badge, button, calendar, checkbox, popover, select, and tabs shadcn/ui primitives it uses for the popular/day badges, the reserve button, the date-range picker, the location and add-on toggles, the dates popover, the location dropdowns, and the insurance tier tabs.
The installed file exports booking20Demo alongside the block: the exact props behind the preview above. Spread it to get a working car rental configurator in one line.
import { Booking20, booking20Demo } from "@/components/beste/block/booking20";
export default function Page() {
return <Booking20 {...booking20Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Booking20 } from "@/components/beste/block/booking20";
export default function Page() {
return (
<Booking20
badge={{ label: "Car rental", variant: "secondary" }}
heading="Rent the <strong>right ride</strong>"
currency="$"
locations={[
{ value: "sfo", label: "San Francisco Airport (SFO)" },
{ value: "lax", label: "Los Angeles Airport (LAX)" },
]}
defaultPickupLocation="sfo"
defaultReturnLocation="sfo"
carClasses={[
{
value: "suv",
label: "Mid-size SUV",
example: "Honda CR-V or similar",
image: "/cars/crv.jpg",
seats: 5,
bags: 4,
transmission: "Automatic",
fuel: "Hybrid",
pricePerDay: 68,
popular: true,
},
]}
insuranceTiers={[
{ value: "basic", label: "Basic", description: "Liability only", pricePerDay: 0 },
]}
addOns={[{ value: "gps", label: "GPS navigation", pricePerDay: 6 }]}
labels={{ bookButton: "Reserve car", perDayLabel: "/ day", totalLabel: "Total" }}
/>
);
}| 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 | – | Lead paragraph under the heading |
currency | string | "$" | Symbol prefixed to every price |
locations | RentalLocation[] | [] | Options for the pickup and return selects |
defaultPickupLocation | string | "" | Initial pickup location value |
defaultReturnLocation | string | "" | Initial return location value (used only when "same location" is off) |
carClasses | CarClass[] | [] | Selectable vehicle cards |
insuranceTiers | InsuranceTier[] | [] | Tabs for the insurance selector |
addOns | RentalAddOn[] | [] | Optional per-day extras in the checkbox grid |
labels | Booking20Labels | {} | All field labels, placeholders, and button copy |
className | string | – | Extra classes for the outer <section> |
type RentalLocation = {
value: string;
label: string;
};
type CarClass = {
value: string;
label: string;
example: string;
image: string;
seats: number;
bags: number;
transmission: string;
fuel: string;
pricePerDay: number;
popular?: boolean;
};
type InsuranceTier = {
value: string;
label: string;
description: string;
pricePerDay: number;
};
type RentalAddOn = {
value: string;
label: string;
pricePerDay: number;
};
type Booking20Labels = {
pickupLocationLabel?: string;
pickupLocationPlaceholder?: string;
returnLocationLabel?: string;
sameLocationLabel?: string;
datesLabel?: string;
datesPlaceholder?: string;
carClassLabel?: string;
insuranceLabel?: string;
addOnsLabel?: string;
popularBadge?: string;
daysLabel?: string;
perDayLabel?: string;
totalLabel?: string;
bookButton?: string;
};useState that is never lifted out: pickup and return location, the "same location" flag (defaults to true), the DateRange, the chosen car class, the insurance tier, and a Set of selected add-on values. There are no onChange callbacks, so the only way to observe state is the internal booking handler.carClasses.find((c) => c.popular)?.value, falling back to the first entry, and the initial insurance is simply the first tier; both fall back to "" when their arrays are empty.totalDays is derived from the date range as Math.max(1, round(diffMs / 86400000)), so a same-day pick still counts as one day, and every subtotal and the grand total are pricePerDay * totalDays. Add-ons sum their per-day prices first, then multiply by the day count.Select is disabled and its value mirrors pickupLocation; the separate returnLocation state is only consulted when the box is unchecked.totalDays > 0; clicking it only calls console.log("Car rental booking:", ...), so persisting or advancing the flow is left to the integrator.heading is injected with dangerouslySetInnerHTML, and its inline <strong> is styled bold and text-primary; pass only trusted markup.<img> tags pointing at whatever image URL you supply, the calendar disables any day before today's midnight, and each of the four sections (car classes, insurance, add-ons) only renders when its array is non-empty.Wiring the form up
This block ships the form markup only; state, validation, and submit are yours to add. Our guide wires the shadcn Field primitives to React Hook Form, TanStack Form, and Formisch on one field system.
booking18
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.
booking17
Ride-share style booking with pickup/dropoff address inputs, schedule now/later toggle, ride class selector (economy/comfort/xl) with ETA and fare, payment method row, and confirm ride CTA. Perfect for taxi, transfer, and shuttle apps.
booking8
Flight search results with airline logos, departure/arrival times, duration, stops, class selector (economy/business/first), baggage info, and price comparison. Airline ticket booking flow.
booking23
Booking cancellation screen with the original booking summary, cancellation reason picker (radio list with optional notes), live refund breakdown based on cancellation policy, confirmation checkbox, and destructive cancel action.
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.
booking1
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.