Booking Bar
What the stay comes to and the one way on, always in reach: the dates, the amount, the action. It floats on a phone and sits beside the rooms on a desk.
Preview
Your stay
No dates yet
Got a group trip in mind?
In the flow here. On a phone, position="fixed" floats it at the foot of the screen.
Usage
import { BookingBar, Button, DateRangePicker, CurrencyPicker } from "zostel-ui";
{/* a phone: floats at the foot of the screen; the page's end keeps room for it */}
<main className="pb-[var(--booking-bar-h)]">…</main>
<BookingBar
position="fixed"
className="lg:hidden"
dates={<DateRangePicker aria-label="Your dates" from={from} to={to} onRangeChange={setDates} />}
aside={<CurrencyPicker currencies={currencies} value={code} onValueChange={setCode} />}
label="Payable now"
amount={payable}
action={<Button onClick={openSummary}>View summary</Button>}
/>
{/* a desk: the same card beside the rooms, 1rem under the header */}
<BookingBar position="sticky" className="hidden lg:block" … />
/* your header's height, once, on :root: every sticky part (this bar, SectionNav) pins under it */
:root { --header-h: 4.5rem; }Props
| Prop | Type | Default | Description |
|---|---|---|---|
position | "static" | "sticky" | "fixed" | "static" | fixed floats it at the foot of the screen, clear of the home bar (z 40, under the header’s 50), and writes how much of the screen it covers to --booking-bar-h; base.css gives that to scroll-padding-bottom, so a field that takes the focus scrolls clear of the bar (WCAG 2.4.11). Give the page’s end the same room (pb-[var(--booking-bar-h)] on the main container), so the last field can scroll above it too. sticky keeps a sidebar card 1rem under the header (top: var(--header-h) + 1rem; set --header-h to your header’s height). static leaves it in the flow. |
dates · aside | ReactNode | — | The top row: a DateRangePicker, and beside it a CurrencyPicker. |
label · amount | ReactNode | — | “From” or “Payable now”, and the amount as you print it (0 included; or words before anything is picked). Together they are a status: a screen reader hears the new total when it changes. |
action | ReactNode | — | The one action: your Button. |
footer | ReactNode | — | A last line under a rule: another way to book. |
aria-label | string | "Your booking" | The bar’s name. |
className | string | — | More classes, merged with the component’s own (tailwind-merge). |
position"static" | "sticky" | "fixed""static"fixed floats it at the foot of the screen, clear of the home bar (z 40, under the header’s 50), and writes how much of the screen it covers to --booking-bar-h; base.css gives that to scroll-padding-bottom, so a field that takes the focus scrolls clear of the bar (WCAG 2.4.11). Give the page’s end the same room (pb-[var(--booking-bar-h)] on the main container), so the last field can scroll above it too. sticky keeps a sidebar card 1rem under the header (top: var(--header-h) + 1rem; set --header-h to your header’s height). static leaves it in the flow.
dates · asideReactNodeThe top row: a DateRangePicker, and beside it a CurrencyPicker.
label · amountReactNode“From” or “Payable now”, and the amount as you print it (0 included; or words before anything is picked). Together they are a status: a screen reader hears the new total when it changes.
actionReactNodeThe one action: your Button.
footerReactNodeA last line under a rule: another way to book.
aria-labelstring"Your booking"The bar’s name.
classNamestringMore classes, merged with the component’s own (tailwind-merge).
On zostel.com
On a phone it rides at the foot of every stay’s page (the dates, the currency, what’s payable and View summary); on a desk the same card sits at the top of the sidebar. Flat, like every panel in the kit: a Beige edge, no shadow, even floating.