Booking
Book the whole house, or a room, in four steps built from every Wave 2 part: dates with the season ribbon, guests and room, your details with the phone confirmed by a code, and a signature to confirm and hold, with breakfast as a switch.
quiet warm playful
Open the live demo
npx susegad add booking
Stands on: Checkbox, Combobox, Core, Core: components, Date range, Engine, Field (TextField, TextArea), Field note, Form, Kernels: booking, Loader, One-time code, Radio group, Select, Signature, Stamp, Stepper, Toast, Toggle, Tokens
The prompt
the prompt
Book a stay: four short steps, a price you can check by hand, and a hold that says it is a prototype.
The prompt
Build a booking form for a whole-house homestay entirely from Susegad UI parts. Put <sg-stepper> inside <sg-form> around a native form, with four steps as fieldsets: dates (<sg-date-range> with the season ribbon), guests and room (<sg-select> for guests, <sg-combobox> for the city they come from, and a required <sg-radio-group> for the room: the whole house, the Sotão, the Balcão or the Quintal), your details (<sg-field> for a name and a mobile number, and <sg-otp> for a six-digit code), and confirm and hold (a required <sg-check> for the house rules, an <sg-toggle> for breakfast, and <sg-signature> to sign). The rate card prices only the whole house, so for a single room show no figure and say the house will confirm its price. Without JavaScript every step must show and the form must validate and submit natively, with POST to a page that keeps nothing and says so, never with GET, which would put people's details in the address bar, with no control that needs scripts and a plain line saying what does. Beside the form, keep a quote card worked out entirely by the pure booking kernels: each band's nights on their own line, then rooms, GST at 18% on its own line, the total and the refundable deposit, in rupees with Indian digit grouping; print whether the rates include GST, and while anything on the card is provisional, say so. When the kernels refuse a stay, keep the person on the dates with the reason in their words. Confirm the phone with a code: since nothing leaves the page, show the code that would have been sent and say so, and keep the details step closed until the code typed matches. A valid submit is the hold: answer it at once with the stay, the guests and "Prototype: nothing is held and nothing is charged.", and let the form land a "Held" stamp in warm and playful. Keep a plain statement that the page holds nothing and charges nothing. Make the walk itself easy to follow: each step is a fieldset whose legend names it, Next checks only that step and focuses the first problem with the browser's own message, focus moves to the new step's legend with "Step 2 of 4" read after it, Back never loses what was typed, and a submit with a problem in an earlier step goes back to that step first. Read out only what changes on the quote card (the total, or the reason a stay is refused), in one polite line. Let the page's register choose every picture through the parts: in quiet, hairlines and words; in warm, the inked calendar, the survey-map walk and the block-print stamp; in playful, bolder ink, footprints along the road and a stamp that presses down. With reduced motion every part shows its finished state.
Words to code
| When you say | Technique | What happens |
|---|---|---|
| Make the walk itself easy to follow | the four-step walk | Each step is a <fieldset> with a <legend>. <sg-stepper> checks the step's own fields with their native validity, moves focus to the next legend described by "Step 2 of 4", and sends a submit with a problem back to its step. Nothing is removed from the form, so every step still submits. |
<sg-stepper> inside <sg-form> | composition | The stepper shows one fieldset at a time and checks it with the form's own validation before Next; the form owns the submit, the summary of problems and the answer. Neither knows the other: they meet in native constraint validation. |
| every step must show without JavaScript | progressive enhancement | With no scripts the stepper is four fieldsets in order and one submit button. The "Send a code" button is created by the script and the code field stays hidden, so nothing on the page needs a script it cannot run. |
| worked out entirely by the pure booking kernels | one source of figures | quote() prices each night by its band and weekday; quoteView only groups and formats them. The calendar reads the same kernels, so it and the card can never disagree. |
| with the reason in their words | constraint validation | <sg-date-range> turns the kernels' reason into a guest's words and sets it as the departure input's custom validity, so Next stops on the dates and the browser says why. |
| for a single room show no figure | no figure it does not have | quoteView returns a room state with no lines and no total when the room is not the whole house, and the card says the house will confirm the price. The stay's rules (minimum nights, taken nights) still apply. |
| show the code that would have been sent | the demo says what it is | codeFor(seed, phone, attempt) makes a seeded six-digit code; the page prints it in a status line that begins "Prototype: nothing leaves this page." |
| keep the details step closed until the code typed matches | custom validity | The number carries "Send a code to confirm this number." until a code is sent; then the code field is required and carries "That code does not match…" until it does. Digits typed in Devanagari, Kannada or full width count, as they do in <sg-otp>. |
| answer it at once | no fake work | The prototype has nothing to wait for, so the hold resolves immediately. There is no timer pretending to be a server. |
The Casa Exemplo booking flow, rebuilt from library parts: four short steps (dates, guests and room, your details, confirm and hold), a price you can check by hand, a phone confirmed by a code, and a signature that holds the dates. It is a prototype and says so: nothing leaves the page, nothing is held and nothing is charged.
| Step | Parts | Asks for |
|---|---|---|
| The walk | <sg-stepper> inside <sg-form> | one step at a time with Back and Next; each step checked before the next; without JavaScript every step shows and the form submits |
| Dates | <sg-date-range prices> | arrival and departure, on a calendar that knows taken nights, turnover mornings and the seasons |
| Guests and room | <sg-select>, <sg-combobox>, <sg-radio-group> | guests (1 to 6), the city you are coming from (optional), the room: the whole house, the Sotão, the Balcão or the Quintal (the room names are still to be confirmed by the house) |
| Your details | <sg-field>, <sg-otp> | your name, a mobile number, and the 6-digit code "sent" to it |
| Confirm and hold | <sg-check>, <sg-toggle>, <sg-signature> | the house rules (required), breakfast (optional, arranged with the house and not in the price), your signature |
| Beside it all | the quote card | every band's nights, rooms, GST on its own line, the total, the deposit |
The answer to the hold is <sg-form>'s: "Held" stamped in warm and playful with "Prototype" under it, as the enquiry's "Not sent" is, and the same words in quiet. A real hold drops the second line (STRINGS.heldDetail).
Files
| File | What it is |
|---|---|
booking.core.js | Pure: the quote as words and figures (nights grouped by band and weeknight or weekend, rooms, GST, total, deposit, provisional flags), the Held words, and the phone code (a seeded six-digit code, read in any script). |
recipe.js | The wiring: mountBooking(root, { rates, blocks, seed }), renderQuote(card, view, reasons) and the phone check. |
recipe.css | The layout, the quote card and the flow's own pieces. |
held.html | Where the form lands without JavaScript. It keeps nothing and says so. |
index.html | The page, which also works as the demo. Links the tokens relatively and reads ?register, ?theme and ?palette. |
recipe.test.js | Tests for the quote and the code, with figures worked by hand from the rate card. |
recipe.check.mjs | Browser checks: the page with JavaScript off (quiet, reduced motion), then the walk with it. |
form-check.json | Valid sample values for tools/form-check.mjs: a stay the rate card accepts, and the code the page shows. Not copied by the CLI. |
Put it on a page
Copy the form and the quote card from index.html, then:
import { mountBooking } from './susegad/recipes/booking/recipe.js';
const tags = ['sg-form', 'sg-stepper', 'sg-date-range', 'sg-select', 'sg-combobox', 'sg-radio-group', 'sg-check', 'sg-field', 'sg-otp', 'sg-toggle', 'sg-signature'];
await Promise.all(tags.map(t => customElements.whenDefined(t)));
mountBooking(document.getElementById('booking'), {
blocks: [{ arrival: '2026-11-13', departure: '2026-11-16', kind: 'booking' }], // nights already taken
});
For a real booking, replace the two prototype parts:
- The code:
mountBookingshows the code on the page because nothing is sent. Send it by SMS from your server instead, and check it there too. - The hold:
mountBookinganswers the form'ssg-submitat once. Create the hold on your server, then resolve with{ message, stamp: 'Held' }, or reject with what went wrong.
Where the figures come from
- Every figure is the booking kernels' own: each band's nights on their own line, rooms, GST at 18% on its own line (or "none below ₹7,500 a night"), the total and the deposit, in lakh grouping.
- The rate card's
provisionalflags reach the page: while any rate or the deposit is provisional, the card says so. The GST mode is printed, not implied. - A refused stay leads the card with "Not bookable as chosen" and the reasons, then its nights, with no total and no deposit, so no figure for an impossible stay can be screenshotted.
- Without JavaScript the card says the house will send the price when they write back, and the minimum stays (3 nights, 4 over Christmas week) are stated under the dates.
- A stay the kernels refuse is refused by the form's own validation, in a guest's words ("Stays over Christmas week are at least 4 nights."), and the walk does not go on.
- The rate card prices the whole house only. Choose a single room and the card shows no figure for it: it says the house will confirm that room's price when they write back.
- The phone step does not pass until the code typed matches the code "sent", and the page says plainly that nothing left it.
- The prototype statement is always on the page, and the Held message repeats it.
Without JavaScript
Every step shows, in order: two date inputs with the window's min and max, a select, a text input with suggestions from a datalist, the room radios, the name and number, the house rules checkbox, the breakfast switch and a typed-name signature. The browser validates them and posts them to held.html, a static page that keeps nothing and says so. POST, never GET: a name, a mobile number and a signature must never end up in the address bar, the browser's history or a server's logs. In your project, point action at your own endpoint. There is no "Send a code" button and no code field, because nothing could send one; a noscript line says the number is confirmed when the house writes back, and another says the price and the hold need JavaScript.
Accessibility
- The stepper moves focus to each step's legend and says "Step 2 of 4"; Next focuses the first problem with the browser's own message.
- One polite line reads out the total for the stay, rather than the whole card, and only when the total changes: typing a name or a number never repeats it. A refused stay is read out once, by the calendar's own list of reasons; the card's line stays quiet about it. The code note is a polite status line.
- The quote card is a labelled
aside; its lines are a description list. - Every part brings its own keyboard and screen reader behaviour: the calendar grid, the combobox listbox, the radio group, the OTP boxes, the signature's typed path.
Verify
node --test packages/recipes/booking/recipe.test.js
node packages/recipes/booking/recipe.check.mjs
MSYS_NO_PATHCONV=1 node tools/form-check.mjs --url /packages/recipes/booking/index.html
MSYS_NO_PATHCONV=1 node tools/form-check.mjs --url /packages/recipes/booking/index.html --no-js
MSYS_NO_PATHCONV=1 node tools/matrix.mjs --url /packages/recipes/booking/index.html --jobs 1
MSYS_NO_PATHCONV=1 node tools/matrix.mjs --url /packages/recipes/booking/index.html --jobs 1 --no-js
MSYS_NO_PATHCONV=1 node tools/axe.mjs --url /packages/recipes/booking/index.html
MSYS_NO_PATHCONV=1 node tools/axe.mjs --url /packages/recipes/booking/index.html --no-js