Susegad UI
Register
Theme
Palette

Recipes

Ask the house (enquiry)

The Casa Exemplo enquiry form rebuilt from library parts: a native form the browser validates, ruled fields that ink as you type, notes in the house's own words, a still line drawing of the house's front elevation in warm and playful, and a send that says plainly it is a prototype and sends nothing.

quiet warm playful

Open the live demo npx susegad add enquiry

Stands on: Combobox, Core, Core: components, Engine, Field (TextField, TextArea), Field note, Form, Loader, One-time code, Select, Stamp, Toast, Tokens

The prompt

the prompt

An enquiry form for a small guesthouse that works without JavaScript and says plainly what happens to your words.

The prompt

Rebuild the Casa Exemplo "Ask the house" enquiry form from Susegad UI parts only. Next to the heading "A question, or dates we can't show yet?" and its short explanation, lay out a native form with <sg-form>, and one <sg-field> for each field: your name (required), phone or email (required; a pattern that accepts either, with a hint that the house replies on WhatsApp), where they are travelling from (an <sg-combobox> of cities that finds older names and other scripts: Bombay, मुंबई, Bangalore), rough dates, people (an <sg-select> of 1 to 6 or "More than 6"), the occasion, and anything else (a ruled textarea). Word the problems in the house's voice with field notes. When someone chooses "More than 6", answer as the house would, as a hint and not an error: "The house sleeps 6. Tell us more below and we'll see what's possible." The site is a prototype, so say plainly before Send that nothing is sent. When someone sends, answer at once with "Prototype: nothing was sent. In the live site this goes to the house on WhatsApp and email." and, in warm and playful, a "Not sent" stamp; never pretend to send. Without JavaScript, the form must still be checked by the browser and submitted: post it to a static page that keeps nothing and says so, never with GET, which would put people's details in the address bar. Let the page's register choose the look through the parts: in quiet, ruled lines and words only; in warm, the rules ink under the words and the stamp is block printed; in playful, wobbling accent ink and a stamp that presses down. Keep it complete by keyboard and screen reader: every field has its visible label and hint, a problem moves focus to the first field and its note says what is wrong, and the answer is said once in a polite status line. Use the Casa palette by default, keep every colour a token, and check it in quiet, with reduced motion, and with JavaScript off. In warm and playful, put the house's front elevation under the explanation as a still line drawing in the register's ink, harvested from the villa redesign's hero drawing: calm, never moving, hidden from assistive technology, and absent in quiet.

Words to code

When you sayTechniqueWhat happens
from Susegad UI parts onlycompositionThe page links the components' CSS and imports field.js, field-note.js and form.js. The recipe's own code is the pieces and the answer (recipe.js, which wires nothing when imported), the demo page's mount (demo.js), and the layout (recipe.css).
a pattern that accepts eithernative validationpattern="[^@\s]+@[^@\s]+.[^@\s]+|+?[0-9][0-9 ]{7,15}": the browser checks it with or without JavaScript. The field note words it; the title is the browser's hint when JavaScript is off.
more than six … a hint and not an errorcompositionPeople is a native <select> (so it cannot be invalid). mountGroupHint() calls setMessage() on a tone="hint" field note when "More than 6" is chosen, and clears it otherwise; the field is never marked invalid.
finds older names and other scriptscomboboxEach <option> in the <datalist> carries data-aliases ("Bombay, मुंबई"). Without JavaScript the browser shows its own suggestions from the same list.
say plainly before Send that nothing is sentsaid before and afterA note above the button says it, and the answer says it again. The browser check confirms no request leaves the page when JavaScript is on.
answer at once … never pretend to sendno pretend workanswer() resolves immediately, so the form goes straight to "sent". No "sending" state, because no work happens.
post it to a static page that keeps nothing … never with GETformsmethod="post" to sent.html, which says the enquiry was not received. The check submits with JavaScript off and reads the request body.
Let the page's register choose the lookregistersThe recipe draws nothing of its own but the house (below). Field, Field note, Form and Stamp each read data-register, so one attribute on the page changes the whole form.
the house's front elevation … a still line drawingharvest, CSS maskfacade.scene.mjs is the villa elevation's buildScene(), copied with engine imports and without its tone and notes. facade.bake.mjs writes its strokes as one SVG (16 KB): integer points in relative moves, a little seeded noise for the hand, vector-effect: non-scaling-stroke so lines stay 0.5 to 1.5 px at any size, and one path per kind of line with its own weight and opacity. .enquiry__facade uses it as a mask over --sg-ink (warm) or --sg-accent-text (playful), so it follows the palette and theme with no JavaScript; display: none in quiet and in forced colours; aria-hidden. A unit test fails if facade.svg drifts from its bake.
the Casa palette by defaulttokensdata-palette="casa" on the root, which ?palette= can change. Every colour in recipe.css is a --sg-* token.

The Casa Exemplo enquiry form, "A question, or dates we can't show yet?", rebuilt entirely from library parts. It is a prototype: it sends nothing, and it says so before and after you press Send.

It composes:

PieceUsed for
<sg-form>the native form: validation, the summary of problems and focus, and the states ("Sent" and, while real work is pending, "Sending")
<sg-field>each ruled line, and the notebook-ruled message
<sg-field-note>what is wrong, in the house's own words, and the hint for more than six people
<sg-select>how many people: 1 to 6, or "More than 6", which brings the house's answer as a hint ("The house sleeps 6. Tell us more below and we'll see what's possible.")
<sg-combobox>where they are travelling from: cities suggested as they type, found by older names and other scripts too ("Bombay", "मुंबई", "Bangalore", "ಬೆಂಗಳೂರು")
<sg-stamp>"Not sent · Prototype", when you send (warm and playful)
<sg-loader>, <sg-toast-region>used by the form when a real send is slow or fails

Files

FileWhat it is
index.htmlThe page. Casa palette by default; ?register, ?theme and ?palette change it.
recipe.jsThe pieces, with no side effects beyond defining the components: mountEnquiry(sgForm, send), mountGroupHint(select, note), the prototype's answer(), which sends nothing, and STRINGS, the words. Importing it wires nothing.
demo.jsThe demo page's wiring: mounts the prototype answer and the group hint on index.html. Your page does this itself, with its own send.
recipe.cssThe page around the form: the heading beside the form on wide screens, the two-column row, the prototype note.
facade.svgThe house's front elevation as a still line drawing (16 KB), shown under the heading in warm and playful.
facade.scene.mjs, facade.bake.mjsWhere the drawing comes from: the elevation's strokes, and the script that bakes them into facade.svg (node packages/recipes/enquiry/facade.bake.mjs). Not needed at run time.
sent.htmlWhere the form lands without JavaScript. It keeps nothing and says so.
enquiry.check.mjsThe browser check: keyboard only, the house's messages, nothing leaving the page, and the page in quiet with reduced motion and JavaScript off.

The drawing of the house

In warm and playful, the heading column carries the house itself: the front elevation of Casa Exemplo as a still line drawing, gable, oculus, the teak door, the arched windows and the potted plants. It is harvested from the villa redesign's hero drawing (elevation.js, where it draws itself in ink over the photograph); here it is the finished linework only, with no photograph, tone or notes, and never moves.

facade.svg is a CSS mask, so it needs no JavaScript and takes the register's ink: --sg-ink in warm, --sg-accent-text in playful, in either theme. It is aria-hidden: the page's words already say what the house is. Quiet hides it, and so does forced colours. For another house, draw its strokes in facade.scene.mjs's shape and bake again.

Without JavaScript

The form is a plain <form action="./sent.html" method="post"> with native constraints:

  • required on name and contact;
  • a pattern on contact that accepts a phone number or an email address, with spaces or hyphens, in ASCII or any Indian numerals (\p{Nd}); with JavaScript the digits are written as ASCII as they are typed (toAsciiDigits from the OTP's core), so the house always receives 98220 12345;
  • people is a <select> of 1 to 6 or "More than 6", so it cannot be wrong; more than six is a question for the house, not an error;
  • "Where are you travelling from?" is the browser's own <input list> with a <datalist>.

The browser checks these itself and shows its own messages (with the title as the hint for the pattern). A valid enquiry is posted to sent.html, a static page that keeps nothing. POST keeps the answers out of the address bar and the server's logs.

Make it send

Import recipe.js and mount your own send. Importing it wires nothing, so the prototype's "Not sent" answer never reaches your form; demo.js is only for the demo page.

import { mountEnquiry, mountGroupHint } from './susegad/recipes/enquiry/recipe.js';

mountGroupHint(document.getElementById('f-group'), document.getElementById('f-group-note'));

mountEnquiry(document.querySelector('sg-form.enquiry__form'), async data => {
  const r = await fetch('/enquire', { method: 'POST', body: data });
  if (!r.ok) throw new Error(`the house's inbox answered ${r.status}`);
  return { message: 'Sent. The house will write back within a day.', stamp: 'Sent' };
});

Point the form's action at the same endpoint, so the page without JavaScript sends too. Then remove the prototype note and sent.html. The form shows "Sending" only while your request is pending, then "Sent", or why it could not send, keeping every word the person typed.