# Build a first session request page with the Solace theme

You are building one page in an existing project: a therapist's page for requesting a first session, with a note that the practice is not an emergency service, a form for a name, an email address, an optional phone number, how to be contacted, in person or online, the times that suit, an optional message and consent, checked in code and replaced by a confirmation, with what happens next and a few lines on confidentiality beside it. Follow this brief closely. `DESIGN.md` decides the tokens: colors, type, spacing steps and corners. This brief decides the layout, its widths included, and which token goes where. If this brief names a color, a type size, a spacing step or a corner that `DESIGN.md` does not have, `DESIGN.md` wins.

## Before you start

1. **Check the theme is installed.** The project should have `DESIGN.md` in its root and the Solace variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/solace.json` and save https://strictthemes.com/themes/solace/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/solace/tailwind.css instead. If the global CSS still imports a font the theme does not use (a new shadcn project imports Geist), remove that import, and uninstall its package if nothing else uses it. Keep every font import the theme's install added, also one for a style no page uses yet.
2. **Read `DESIGN.md` from start to finish.** It is the source of truth for every visual decision.
3. **Look at the project before writing code.** Use its framework, router and file layout. For controls (buttons, inputs, tabs, menus, dialogs, accordions, data tables) use the components in `components/ui`, and add a missing one with `npx shadcn@latest add <name>` rather than writing your own, unless this brief names a plain element. Layout is plain elements with theme classes: sections, panels, figures and lists need no component.
4. **Teach `cn()` the theme's type classes.** shadcn's class merger does not know `text-display` or `text-h1`. It takes them for colors, so `cn("text-label text-muted-foreground")` silently drops one of the two, and a type class passed to a stock component does not replace its size. In a new shadcn project, `lib/utils.ts` only passes `cn` on from the `cn` package. Unless the file already holds the lines below, change it to:

   ```ts
   import { createCn } from "cn/config"

   // The theme's type classes are font sizes, not colors.
   export const cn = createCn({
     extend: {
       classGroups: {
         "font-size": [{ text: ["display", "h1", "h2", "body", "label"] }],
       },
     },
   })
   ```

   Then make every file in `components/ui` use it: where one has `import { cn } from "cn"`, change that line to import `cn` from the project's utils file (`@/lib/utils` in a default setup). Do the same for each component you add later. If the project uses `tailwind-merge` instead of the `cn` package, its components already import from the utils file: pass the same object to `extendTailwindMerge` and keep `clsx`.
5. **Write real content.** If the user gave you details about their product, use them. Otherwise use the sample product in this brief and write the rest of the copy yourself: specific and plain, the way the samples read. Invent the company and people names; do not use real ones. Never use lorem ipsum. A link with no destination yet points to `#`. Set the page's `<title>` and meta description.

## The theme in short

- **Fonts:** Literata for headings, Mulish for body text, Xanh Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 56px/400, `text-h1` 38px/400, `text-h2` 24px/500, `text-body` 17px/400, `text-label` 14px/400.
- **Font per size:** a type class does not set the font family. Headings (`<h1>` to `<h6>`) get the heading font on their own; on any other element add the font class: `font-heading` with `text-display`, `text-h1`, `text-h2`; `font-mono` with `text-label`.
- **Spacing scale**, with the Tailwind step in brackets: 4px (`1`), 8px (`2`), 16px (`4`), 28px (`7`), 48px (`12`), 112px (`28`).
- **Corners:** `rounded-sm` 6px, `rounded-md` 12px, `rounded-xl` 20px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 12px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #22223B, on-primary #F2E9E4, secondary #C9ADA7, on-secondary #22223B, tertiary #9A8C98, on-tertiary #22223B, neutral #4A4E69, background #F2E9E4, surface #FAF5F2, foreground #22223B, border #DFD0C9, error #9B2C2C.

### How Solace pages are laid out

- **Container:** one `<div>` wraps the whole page and is the container: `mx-auto max-w-[1136px] px-4 md:px-7`, plus `text-body text-foreground`. From `md` up the content is at most 1080px wide, the grid in `DESIGN.md`, with 28px at each side. This width is the one arbitrary value allowed. Inside it sit the `<header>`, a `<main>` holding everything between the two, and the `<footer>`.
- **Grid:** a section's content sits in a grid: `grid grid-cols-1 md:grid-cols-12 md:gap-x-7`. The grid sets columns only, never `gap-y-*`: every vertical distance comes from the rhythm rule. Below `md` every grid item takes the full width. From `md` up every grid item gets `md:col-span-*`, because an item without one takes a single column: "columns 1–12" is `md:col-span-12`. It also gets `md:col-start-*` when it starts after column 1, and `md:col-start-1` when it must start a new row under an item that leaves room beside it; or the grid puts it in that room. Where a brief puts "a section's content in columns 1–7", the `<section>` is the grid and the content is one `<div>` with those column classes. Where a list "is itself the grid", the `<section>` is not a grid: the heading above the list sits directly in it.
- **Reading column and note:** running text, lists and forms sit in seven of the twelve columns (`md:col-span-7`), or the columns this brief gives. Beside them, where this brief asks for one, sits one short note in columns 9–12 (`md:col-start-9 md:col-span-4`), with `mt-12 md:mt-0`, so on a phone it comes after the text. Leave the columns this brief does not fill empty, and center nothing to fill them: the empty columns are the calm of the page.
- **Vertical rhythm:** written as a top margin on the lower element (`mt-*`), never as `space-y-*`, `gap-y-*`, a bottom margin or padding, except the padding of a card, a list entry or a table cell, which these notes give. Between a label and the one thing it names (a field's label and its field, a name and the line under it, a term and its value when they stack): `mt-2`. Between a heading and the text under it, between paragraphs, and from a label line to the heading under it: `mt-4`. Between blocks inside a section (a paragraph and a button, a heading and a list or a table, a card and the text after it, two fields of a form): `mt-7`. Between two parts of a section that each have their own heading: `mt-12`. Where two grid items share a row from `md` up, the second has its phone margin and `md:mt-0`. Inside a flex row (the header, a portrait and a name, a row of badges) the space is the row's `gap-*` as this brief states it. Where this brief gives an element its own margin, it replaces the one from this list.
- **Sections:** `<main>` holds a stack of `<section>` elements, each with `aria-labelledby` naming its heading. The first has `mt-12 md:mt-28`. Every other section has `mt-28`: 112px, at every width. Sections are divided by that space alone: no hairline, no fill and no border between them. A section's first element takes no top margin of its own.
- **Header:** the `<header>` is itself the row, with no border: `flex flex-wrap items-center gap-x-7 py-4`. On the left the therapist's name, `Nora Ellingham`, a link to `/` with `inline-flex h-11 items-center font-heading text-h2 text-foreground`, never marked as the current page. It is not a heading element. Then a `<nav>` with the `aria-label` "Main" and `w-full md:ml-auto md:w-auto`, so from `md` up it sits at the row's right end and below `md` it takes a line of its own under the name. It holds a `<ul>` with `flex flex-wrap gap-x-4 md:gap-x-7` and four links, in this order: "Approach" (`/approach`), "About" (`/about`), "Fees" (`/fees`) and "First session" (`/first-session`). Each is `inline-flex h-11 items-center text-body text-foreground decoration-1 underline-offset-4 hover:underline`. Each brief names the current one, if any: it keeps its address and adds `aria-current="page"` and `underline`. The four fit on one line at 375px, so there is no "Menu" button. The header holds no button: the page's one action is in the page.
- **Footer:** a `<footer>` with `mt-28 border-t border-border py-12`, the one hairline that runs the width of a page. Inside it a grid like the one above, with two parts:
  - Columns 1–7 (`md:col-span-7`): the name as a `<p>` in `font-heading text-h2`, `Nora Ellingham` (not a heading and not a link); with `mt-2` a label line in `text-muted-foreground`, `Registered psychotherapist · Larkmere and online`; with `mt-7` an `<address>` with `not-italic` in `text-body`, one line, `The Garden Room, 14 Orchard Row, Larkmere LK2 4DN`; with `mt-2` a `<p>` holding the email address `hello@ellingham.example` as a `mailto:` link, styled as a link in running text.
  - Columns 9–12 (`md:col-start-9 md:col-span-4`, with `mt-7 md:mt-0`): a `<nav>` with the `aria-label` "Footer" holding a `<ul>` of the header's four links, one under the other, in the same order and with the same classes. The current page is marked here too.
  - Under the grid, with `mt-7`, a `<p>` in `text-body text-foreground`, at most `max-w-xl`: "This practice is not an emergency service. In an emergency, call your local emergency number or go to the nearest emergency department." Then with `mt-4` a label line in `text-muted-foreground`: `© 2027 Nora Ellingham`.
- **The practice:** Nora Ellingham is an invented registered psychotherapist who works alone, with adults, one to one, in private practice since 2014. She sees people in The Garden Room, 14 Orchard Row, Larkmere LK2 4DN (an invented town) and online by video. Sessions are held on Tuesday 09:00–17:00 (in person), Wednesday 12:00–20:00 (in person or online), Thursday 09:00–17:00 (online) and Friday 09:00–13:00 (online). A session is 50 minutes and costs £68. It starts with an introductory call of 20 minutes, by phone or video, which is free. A longer session of 80 minutes is £102, and a reduced-fee session is £42. She replies to a request within two working days, by email from `hello@ellingham.example`; the practice has no public phone number. Cancelling is free with 48 hours' notice. She sends one invoice on the last Friday of each month, paid by bank transfer within 14 days. She speaks English and Dutch. Every page uses these names, times and fees as they are here. Name no real person, place, register, insurer, school or helpline.
- **Writing:** the words are part of the design. Nora writes as one person to another: "I", never "we" for the practice, and "you". Short sentences and plain words. No exclamation marks, no urgency ("book now", "today only", "limited places"), no promise of a result ("heal", "fix", "overcome", "transform"), no praise of herself. Copy that this brief gives in quotation marks is used word for word. Where you write a sentence yourself, write it the same way, and say what happens rather than how it will feel.
- **Headings:** the `<h1>` is `text-h1`, and `text-display` from `md` up where this brief says so (`text-h1 md:text-display`). An `<h2>` is `text-h2`, and so is an `<h3>`, unless this brief gives another size. Headings get the heading font on their own; any other element in `text-display`, `text-h1` or `text-h2` adds `font-heading`. Sentence case, no full stop. Nothing on these pages is bold: the theme has two weights, 400 and the 500 that `text-h2` and `font-medium` give. Never `font-semibold` or `font-bold`, no `<strong>` or `<b>`, and a `<th>`, which is bold by default, gets `font-normal`.
- **The italic:** both the heading font and the body font have a true italic. It is the page's one emphasis, and it is used in two places only. A phrase inside a heading, where this brief marks it, is an `<em>` with `italic`. Words a client said are a `<blockquote>` whose `<p>` is `font-heading text-h2 italic`. Nothing else is italic: an `<address>` is italic by default, so every `<address>` gets `not-italic`.
- **Labels:** times, lengths of sessions, fees, days of the week, dates, step numbers and places in a short line are labels: `font-mono text-label`, in sentence case. Nothing is uppercased, in the text or with a class, and no `tracking-*` class is added. A label is a short fragment. Join the parts of a label line with ` · `. A label on its own line is a block element such as a `<p>`. Nav links, button text, field labels and legends are not labels: they are in the body font.
- **Captions:** a caption under a quotation or a table and the helper text of a field use the label style in `text-muted-foreground` (`font-mono text-label text-muted-foreground`) and may be a short sentence or two.
- **Text color:** headings, body text, nav links, names, field labels, fees and times in a table are `text-foreground`. Label lines above or under a heading, terms in a `<dl>`, column headers of a table, step numbers and captions are `text-muted-foreground`. Muted text sits only on `bg-background` or `bg-card`. Paragraphs are never muted. No other text colors, except `text-destructive` on an error line and the colors a button, a badge and a portrait bring.
- **Measure:** a paragraph of body text is at most `max-w-xl` wide, however many columns it sits in. A heading may run wider.
- **Times, dates and fees:** a clock time is 24-hour with two-digit hours, `09:00`, and a span of hours has an en dash and no spaces, `09:00–17:00`. A time that stands in a table or a label line is a `<time>` with a `dateTime` (`<time dateTime="09:00">09:00</time>`), with `tabular-nums`. A time or a length inside a sentence, a field's label or a message is plain text. A length is written out: `50 minutes`. A fee is whole pounds with the pound sign, `£68`, or the word `Free`. A date is written `1 January 2027`, and in a label line it is a `<time>` too (`dateTime="2027-01-01"`). A year is a `<time>` only where this brief says so; the copyright year is plain text.
- **Links:** a link in running text is `text-foreground underline decoration-1 underline-offset-4 hover:text-muted-foreground`, and inherits its size and font. Links are the text color; nothing is colored to mark it as a link. Every link gets `transition-colors duration-150`. A link that stands alone at the end of a part is a `<p>` holding such a link, with no icon. A link whose destination this brief does not give points to `#`. The class lists in these notes and briefs leave out the focus outline: add the outline classes from the rules to every link, every native radio and every plain `<button>`.
- **Buttons:** the shadcn `Button` with `className="h-auto px-7 py-2 text-body"`: the body font, sentence case, two to four words, 12px corners (its stock `rounded-lg`) and about 46px tall. It keeps its stock `font-medium`: weight 500 is the heaviest weight the theme's type scale has, so no button is `font-semibold`. The default variant (indigo, rose taupe in dark mode) is for the page's one main action: one on a page, and this brief names it. Any other button is `variant="secondary"` (rose taupe, slate in dark mode). In `components/ui/button.tsx`, replace that variant's stock `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, not beside it: the 5% mix cannot be told from the resting color. Make this change on every page, also one with no secondary button, so every page's `button.tsx` is the same. Do not use the `outline`, `ghost`, `link` or `destructive` variants, or the `xs`, `sm` or icon sizes. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A call to action that goes to another page is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "h-auto px-7 py-2 text-body")`, because `buttonVariants` alone does not merge classes. No icon inside a button.
- **The one action:** a page asks for one thing. Where this brief ends a page with the action, it is a card from these notes holding an `<h2>`, with `mt-4` one or two sentences that say what happens after it, and with `mt-7` a `<div>` holding the default button "Request a first session", an `<a>` to `/first-session`. Nothing on a page hurries: no countdown, no "limited", no banner, no pop-up.
- **Added components:** a component you add with `npx shadcn@latest add` during the build (an `Input`, a `Textarea`, a `Checkbox`, a `Badge`) gets the `cn` import from the setup steps, the focus outline only if it takes focus on the page, and the edits these notes give its file.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="h-auto border-muted-foreground px-4 py-2 text-body md:text-body"`; a `Textarea` adds `min-h-32`. The stock `border-input` is too faint on blush; `border-muted-foreground` reaches 3:1 in both modes. They keep their stock 12px corner (`rounded-lg`), the fill they ship with (in dark mode too), the stock `focus-visible:border-ring` and the stock `aria-invalid:border-destructive`. Every field has a visible `<label>` above it with `block text-body font-medium text-foreground`, tied to it with `htmlFor`. Under the label, where this brief gives one, a helper line with `mt-2`: a caption in a `<p>` with an `id`, which the field names in `aria-describedby` at all times. The field has `mt-2` under the label or the helper line. Fields follow each other with `mt-7`, on the wrapper `<div>` that holds a label, its helper line, its field and its error. A field that may be left empty says so in its label: `Phone number (optional)`. No placeholder text.
- **Groups of choices:** a set of radios or checkboxes that answers one question is a `<fieldset>` with `min-w-0`, inside its wrapper `<div>`, with a `<legend>` as its first child in `text-body font-medium text-foreground`. A helper line, where this brief gives one, follows with `mt-2`, then the options, the first with `mt-2`, then the error line while there is one. The fieldset's `aria-describedby` names the helper line and, while it shows, the error line.
- **Radios:** native `<input type="radio">` elements, drawn round with classes, not the shadcn `RadioGroup`: `size-5 shrink-0 appearance-none rounded-full border border-muted-foreground checked:border-6 checked:border-primary`, so the chosen one is a thick indigo ring (rose taupe in dark mode) around a hole. Each option is a row, `flex min-h-11 items-center gap-4`, holding the input with an `id` and its `<label>` with `htmlFor` in `text-body text-foreground`. All options of a group share a `name`. No option is chosen when the page opens.
- **Checkboxes:** the shadcn `Checkbox` with `className="size-5"`. In `components/ui/checkbox.tsx`, change its `rounded-[4px]` to `rounded-sm` and its `border-input` to `border-muted-foreground`, once, so it has the theme's 6px corner and a border that can be seen (where the file ships another corner or border class, change that one). Its stock check icon and checked fill (`bg-primary`) stay. In a group, each option is a row, `flex min-h-11 items-center gap-4`. A checkbox whose label is a sentence that may wrap is a row with `flex items-start gap-4`, and `mt-1` on the checkbox so it lines up with the first line. The `<label>` is `text-body text-foreground`. Give the `Checkbox` an `id` and the `<label>` `htmlFor` with it, so a click on the words ticks the box; give the `<label>` an `id` too, and the `Checkbox` `aria-labelledby` naming it, because in a Base UI project the `Checkbox`'s `id` lands on a hidden input and the visible box would otherwise have no name.
- **Errors:** an error is a line under its field or under the last option of its group, with `mt-2`: a `<p>` with an `id` and `flex items-start gap-2 text-body text-destructive`, holding the `CircleAlert` icon (`mt-1 shrink-0`) and one or two short sentences that say how to put it right, in the same calm voice as the rest. A field gets `aria-invalid` and adds the line's `id` to its `aria-describedby` (after the helper line's); a group names the line in its fieldset's `aria-describedby`. Fields are checked in code when the form is sent, with `noValidate` on the `<form>`; before the first send no error shows. Focus moves to the first field with an error, or to the first option of the first group with one. Render the error lines first and move focus after that render, in an effect or a `requestAnimationFrame`, so the field's description is there when it is read. An error line and its field's `aria-invalid` are removed when that field changes (on `input` or `change`; for a `Checkbox`, on `onCheckedChange`; for a group, when any of its options changes). Every other error stays until the next send. An email is well formed when, trimmed, it matches `/^[^\s@]+@[^\s@]+\.[^\s@]+$/`.
- **Forms:** a form sits in a card, and the card is the wrapper for the form and the confirmation that replaces it: a card from these notes holding the `<form>` and, after it, the status element. The `<form>` has `noValidate` and an `aria-labelledby` naming the heading inside it. Its send button sits under the last field with `mt-7`, full width below `md` (`w-full md:w-auto`).
- **Confirmations:** a message that replaces a form goes into an element with `role="status"` that is in the card from the start, empty, so that its message is announced. It is never hidden (no `hidden` attribute, no `empty:hidden`) and has no classes and no margin; empty, it takes no space. The form is removed when the message appears, so the message stands where the form stood. The message is a `<div>` rendered only once there is one: an `<h2>` in `text-h2`, then with `mt-4` the paragraphs this brief gives, in `text-body`, each a `<p>`, `mt-4` apart. Give the heading `tabIndex={-1}` and move focus to it, because the button that had focus is gone. No colored box, no tick icon.
- **Cards:** a card is a plain element with `rounded-xl border border-border bg-card p-7`: paler blush, 20px corners, 28px padding, a 1px border, no shadow. Not the shadcn `Card`, which ships its own padding and a ring. Inside it the first element takes no top margin. Use one only where this brief asks for it. Never a card inside a card, and nothing with a border and corners inside a card except the fields of a form: divide a card's parts with space.
- **Portraits:** there are no image files. A portrait is a round fill with the person's initials: a `<div>` with `flex size-18 shrink-0 items-center justify-center rounded-full bg-secondary font-heading text-h2 text-secondary-foreground`, `role="img"` and the `aria-label` "Portrait of Nora Ellingham", holding one `<span>` with `aria-hidden="true"`, `NE`. It is 72px across, the largest full round the theme allows, so it is never made larger. Nothing else is drawn in it and nothing is drawn around it: no icon, no ring, no leaf, no wave. It stands beside the name and a line of training, in a row with `flex items-center gap-4`, and never inside a button or a link.
- **Badges:** the shadcn `Badge` (`npx shadcn@latest add badge`) with `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label font-normal text-tertiary-foreground"`: mauve grey with ink, in both modes. One or two words in sentence case, no icon: "Online", "In person", "Evenings". Only where this brief puts one. Nothing else on a page is `bg-tertiary`.
- **Lists with hairlines:** a list where this brief gives each entry a hairline (what people bring, training, questions) is a `<ul>`, `<ol>` or `<dl>` with `list-none`, no padding and no space between its entries; each entry has `border-t border-border py-4`, and the list a `border-b border-border` that closes it. Where this brief lays an entry out in columns, the entry itself carries the grid classes beside these.
- **Description lists:** a `<dl>` where a term stands beside its value is a list with hairlines whose rows are `<div>` elements with `grid grid-cols-3 items-baseline gap-x-4` beside the entry's classes: the `<dt>` is a label in `text-muted-foreground`, the `<dd>` takes the other two columns (`col-span-2`) in `text-body`. This holds at 375px too.
- **Tables:** a plain `<table>` with `w-full border-collapse border-b border-border text-left`, not the shadcn `Table`. Its first child is a `<caption>` with `sr-only` that names it; the heading above it names it on screen. Header cells are `<th scope="col">` with `py-2 pr-4 font-mono text-label font-normal text-muted-foreground`. Each row of the `<tbody>` has `border-t border-border`. Its first cell is a `<th scope="row">` with `py-4 pr-4 align-baseline text-body font-normal text-foreground`; the other cells are `<td>` with `py-4 pr-4 align-baseline`. The last cell of every row, the header row included, has `pr-0` in place of `pr-4`. No stripes, no hover fill, no outer box. The table is not in a scroll box: the cells this brief names get `whitespace-nowrap` and the others wrap, so it fits at 375px.
- **Icons:** from the project's icon set, `lucide-react`, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`. Use them only where this brief or these notes name one: `CircleAlert` in an error line, `Plus` and `Minus` on a question. Never emoji, and no drawn decoration.
- **Corners:** `rounded-sm` (6px) for badges and checkboxes; the stock 12px of the `Button`, `Input` and `Textarea` (their `rounded-lg`, which this theme sets to 12px); `rounded-xl` (20px, the `lg` corner in `DESIGN.md`) for cards. `rounded-full` only for a portrait and a radio.
- **Surfaces:** flat and soft. A card is the only surface, and it has a border and no shadow. No shadows, blur, gradients or glows in the code you write, and no colored band behind a section. A stock component that floats over the page keeps the one soft shadow it ships with; these pages use none.
- **Motion:** in the code you write, the only transition is color, and nothing moves on scroll or on load. Stock components keep the motion they ship with, the `Button`'s included.

## The page

Where this section and the layout notes above differ, this section wins.

Sample content, if the user gave none: a request for a first session with **Nora Ellingham**, an invented registered psychotherapist who works alone, with adults, in a garden room in Larkmere, an invented town, and online by video. She reads every request herself and replies within two working days, by email from `hello@ellingham.example` or by phone if asked. Then comes a free call of 20 minutes, and after it, if both wish, a first session of 50 minutes at £68. Sessions are held on Tuesday, Wednesday, Thursday and Friday, between 09:00 and 17:00, and on Wednesday until 20:00. The practice is not an emergency service. The copy below is given word for word: use it as it is, error and confirmation messages included.

Build these parts in order.

### 1. Header

The header from the layout notes. "First session" is the current page, in the header and in the footer.

### 2. Opening

The first section, in the grid, with its `aria-labelledby` naming the `<h1>`:

- A label line in `text-muted-foreground`, columns 1–12 (`md:col-span-12`): `First step · A call of 20 minutes · Free`.
- With `mt-4`, the `<h1>` in `text-h1 md:text-display`, columns 1–9 (`md:col-span-9`): "Request a first session".
- With `mt-7`, a `<p>` in columns 1–7 (`md:col-start-1 md:col-span-7`, which overlap the `<h1>`'s, so it starts a row under it), at most `max-w-xl`, in `text-body`: "Fill in what you can. I read every request myself, and I reply within two working days."

### 3. Your request

A section in the grid, with the `aria-label` "Your request" (here, in place of `aria-labelledby`, because the form's heading leaves the page with the form), and two parts.

**The request**, in columns 1–7 (`md:col-span-7`), holding the note and, under it, the form.

The note about emergencies comes first: a card from the layout notes, a `<div>` with `role="note"` and `aria-labelledby` naming its heading. It holds no icon, no link and no phone number.

- An `<h2>`: "This is not an emergency service".
- With `mt-4`, a `<p>` in `text-body`, at most `max-w-xl`: "I read requests on working days, and it can take me two days to reply. If you are in immediate danger, or you think you might harm yourself or someone else, please do not wait for me. Call your local emergency number or go to the nearest emergency department."

With `mt-7`, the form's card, as the Forms note describes it: a card holding the `<form>` and, after it, the empty `role="status"` element. Add the fields with `npx shadcn@latest add input textarea checkbox`. In the `<form>`, in this order:

- An `<h2>` with an `id`: "Your request".
- With `mt-4`, a `<p>` in `text-body`: "Your name, an email address and the choices below are needed. The rest is up to you."
- The fields, each in its own wrapper with `mt-7`:
  1. **Name:** the label "Your name", the helper line "A first name is enough.", an `Input` with `autoComplete="name"`.
  2. **Email:** the label "Email address", an `Input` of type `email` with `autoComplete="email"`.
  3. **Phone:** the label "Phone number (optional)", the helper line "Only needed if you would like me to call.", an `Input` of type `tel` with `autoComplete="tel"`.
  4. **How to reply:** a group of choices with the `<legend>` "How should I reply?" and the helper line "If I call and you cannot answer, I leave no message." Two radios with the `name` `reply`: "By email" and "By phone".
  5. **Where:** a group of choices with the `<legend>` "In person or online?" Three radios with the `name` `place`: "In person, in Larkmere", "Online, by video" and "Either is fine".
  6. **Times:** a group of choices with the `<legend>` "Which times could suit you?" and the helper line "Tick all that could work." Three checkboxes, each in a row of a group: "Mornings, 09:00 to 12:00", "Afternoons, 12:00 to 17:00" and "Wednesday evenings, 17:00 to 20:00".
  7. **Message:** the label "What brings you here? (optional)", the helper line "A sentence is enough. You can also leave this empty and tell me when we speak.", a `Textarea` with `maxLength={1000}`.
  8. **Consent:** a wrapper holding one checkbox row whose label is a sentence, as the Checkboxes note gives it, and under the row its error line while there is one. The `<label>`: "I agree that Nora Ellingham may keep these details to reply to my request."
- With `mt-7`, the default button "Send my request", `type="submit"`. The page's one default button.
- With `mt-4`, a caption from the layout notes, a `<p>`: "Nothing is booked or charged when you send this."

**Beside it**, in columns 9–12 (`md:col-start-9 md:col-span-4`), with `mt-12 md:mt-0`: a `<div>` with no card, holding two parts.

- An `<h2>`: "What happens next".
- With `mt-4`, an `<ol>` as a list with hairlines (see the layout notes), three entries. Each `<li>` holds a label line in `text-muted-foreground` and, with `mt-2`, a `<p>` in `text-body`:
  1. `Within 2 working days`: "I reply, by email or by phone, as you chose."
  2. `20 minutes · Free`: "We speak, by phone or video. You can ask me anything."
  3. `50 minutes · £68`: "If we both wish, we arrange a first session."
- With `mt-12`, an `<h2>`: "Confidentiality".
- With `mt-4`, three paragraphs in `text-body`, the second and third with `mt-4`: "What you write here comes only to me. Nobody else reads it."; "I use it to reply to you, and for nothing else. I send no newsletters."; "If we do not go on to work together, I delete your details within 30 days."

### 4. Footer

The footer from the layout notes.

### On a phone

Every part is one column, in this order: the opening, the note about emergencies, the form, what happens next, confidentiality. The cards keep their 28px padding, and the fields take the full width inside the form's card. Each radio and each checkbox of a group stands in a row at least 44px tall. The send button is full width. What happens next starts 48px under the form's card.

### Behavior

- Keep every value in component state: the name, the email address, the phone number, how to reply (none, email or phone), the place (none, in person, online or either), the three times, the message and the consent.
- **Sending:** check the form in code, in this order, and show one error per field or group, the first that applies, as the Errors note says:
  - Name, empty or only spaces: "Write your name. A first name is enough."
  - Email address, empty: "Write your email address, so that I can reply." Malformed: "Check the email address. It needs an @ and a domain, like name@example.com."
  - Phone number, empty while By phone is chosen: "Write a phone number, or choose By email below." Not empty, but once its spaces are taken out it is not 10 to 15 digits with at most one `+` in front: "Check the phone number. It needs 10 to 15 digits. You can also leave it empty."
  - How to reply, none chosen: "Choose email or phone."
  - In person or online, none chosen: "Choose one of the three. Either is fine."
  - Times, none ticked: "Tick at least one time. You can tick them all."
  - Consent, not ticked: "Tick the box, so that I may keep your details and reply."
  - The message is never checked.
- **Focus:** focus moves to the first field with an error. For a group of radios that is its first radio; for the times, the first checkbox; for the consent, its checkbox. To focus a `Checkbox`, hold a ref on it and call `focus()`. The consent's `Checkbox` gets `aria-invalid` and names its error line in `aria-describedby` while the error shows.
- **Errors going away:** as the Errors note says, and one more: the phone number's error about a missing number also goes when By email is chosen.
- **Confirmation:** when all is good, the form is replaced by the message in the status element, as the Confirmations note says. The `<h2>`, with `tabIndex={-1}` and focus moved to it: "Thank you, Sam. Your request is with me.", with the name as typed, trimmed. Then three paragraphs:
  - Where the reply goes. By email: "I will write to sam@example.com within two working days." By phone: "I will call 01632 960 417 within two working days. If I do not reach you, I will write to sam@example.com." The address and the number are the ones typed, trimmed; the two here are samples.
  - "I will offer you one or two times for a call of 20 minutes. It is free, and nothing is booked until we have spoken."
  - "You do not need to do anything else for now."

  After the status element, in the same card and only while the message shows, with `mt-7`, a `<p>` holding one link in running text: "Read how sessions work", to `/approach`. No button follows, and the form does not come back.
- The note about emergencies and the part beside the form stay as they are. Nothing is sent anywhere: the confirmation is shown in the page.

## Rules

- **Colors:** only the theme's colors, through the semantic classes (`bg-background`, `text-foreground`, `bg-card`, `bg-primary`, `text-primary-foreground`, `text-muted-foreground`, `border-border` and so on). No hex, rgb or named colors in components, and no Tailwind palette classes such as `bg-blue-500` or `text-gray-600`.
- **Spacing:** padding, margin and gap use only the scale's steps (`1`, `2`, `4`, `7`, `12`, `28`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-6`. `auto` margins are fine. Widths and heights such as `w-64` or `size-4` are sizes, not spacing. This applies to the code you write: a stock component keeps the spacing inside it, unless this brief changes it.
- **Type:** every piece of text you write gets its size from one of `text-display`, `text-h1`, `text-h2`, `text-body`, `text-label`, set on the element or on a parent it inherits from. Put `text-body` on the outermost element of the page you write. No other sizes. A stock shadcn component keeps the size it ships with, unless `DESIGN.md` or this brief gives that component its own style. A type class does not replace a weight class: where a stock component ships `font-medium` and the text should be regular, add `font-normal`.
- **Fonts:** only the theme's fonts, through `font-sans`, `font-heading` and `font-mono`. Do not add a font.
- **Corners:** only the `rounded-*` classes, which the theme already sets. No arbitrary radius, and `rounded-full` only where `DESIGN.md` or this brief names a full round, such as an avatar or a status dot. The theme allows a full round on a box up to 72px across; Strict Check reports a larger one, and a pill counts by its height.
- **Contrast:** all text meets WCAG AA against its background.
- **Stock components:** a component from `components/ui`, and an `<a>` styled with `buttonVariants`, keeps the hover, pressed and disabled colors and the motion it ships with, unless this brief changes them. They are the theme's colors under other names.
- **Counts:** a number with a noun reads right at one: "1 set", not "1 sets". Where this brief gives the words for only one of the two, write the other the same way.
- **Sizes from data:** a height or a width that comes from data (a chart bar, a meter) is set with an inline `style`. That is the one use for an inline style.
- **Focus:** every link and control shows a 2px outline on keyboard focus: `focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`. Keep `focus-visible:outline-solid` in it: in Tailwind 4 an `outline-none` on the same element, which stock components carry, otherwise hides the outline. An element that only gets focus from code (a heading or a message with `tabIndex={-1}`) gets the same outline written with `focus:` in place of `focus-visible:` (`focus:outline-solid focus:outline-2 focus:outline-offset-2 focus:outline-ring`): after a mouse click, a browser does not match `:focus-visible` on an element that code focuses. A link, a button or a field that code focuses keeps the `focus-visible:` classes it has: after a mouse click it shows no outline, which is how a browser treats every control, and is right. A stock component that ships its own focus ring (`Button`, `buttonVariants`, `Input`, `Textarea`, `Checkbox`, `Switch`, `AccordionTrigger`, the close button of a `Sheet`) must lose that ring. Do it once, in the component's file in `components/ui`, which is yours to change: replace its `focus-visible:ring-*` classes with the outline classes, and in a field replace its `aria-invalid:ring-*` classes, the `dark:` ones included, with `aria-invalid:ring-0`, or a field with an error shows a tinted ring. `Button` has two more sets. Its base classes also carry `aria-invalid:ring-*` classes: replace them with `aria-invalid:ring-0`, as in a field. Its `destructive` variant carries `focus-visible:ring-*` classes of its own, the `dark:` one included: delete them and put nothing in their place, because the base classes already give every variant the outline. Wherever this rule says replace, change the classes in the file and do not add `ring-0` beside them: an added class only wins where it comes later and passes through `cn`. The stock `focus-visible:border-ring` can stay. These ring changes go only into the files of components that this page uses and that can take focus on it: a `Badge` that is not a link keeps its ring classes. The `cn` import from the setup steps is another matter and goes into every file. A box that scrolls sideways (a code block, a wide table) gets `tabIndex={0}`, `role="region"`, an `aria-label` and the same outline, also at widths where it happens to fit. Where a focusable box touches the edges of a parent that has `overflow-hidden`, draw its outline inside it: `focus-visible:-outline-offset-2` in place of `focus-visible:outline-offset-2`.
- **Not allowed:** purple or multi-color gradients; emoji used as icons or bullets; glows and colored shadows.
- **Avoid:** a card inside a card.
- **Dark mode:** the page must work with the `dark` class on `<html>`. Because every color comes from a semantic class, this needs no extra code; do not write `dark:` color overrides. A new shadcn project follows the system's mode and switches on the `d` key: leave that as it is, and add no mode switch of your own. If the page has a native `<select>` or a date or time field, tell the browser the mode, or its list and its picker stay light in dark mode: in the global CSS, give `:root` `color-scheme: light` and `.dark` `color-scheme: dark`.

From the theme's own do's and don'ts:

- Do write as one person speaks to another: short sentences, plain words, "you" and "I".
- Do give one action on a page and say what happens after it.
- Do say fees, lengths and how to cancel plainly.
- Don't use urgency: no countdowns, no "limited places", no pop-ups.
- Don't promise results, and don't decorate with leaves, waves or sunrises drawn in code.
- Don't use gradients, glows or a card inside a card.
- Don't introduce a color, size or radius that is not a token above.

## Check before you finish

Go through this list against the code you wrote, and fix anything that fails.

- [ ] Every field has a visible label; how to reply and in person or online are fieldsets of native radio buttons drawn round, the times are a fieldset of three checkboxes, and the consent is a checkbox whose label is tied to it.
- [ ] Sending the form empty shows an error line with an icon under the name, the email address, both radio groups, the times and the consent, each saying how to put it right, and moves focus to the name field.
- [ ] Choosing By phone and leaving the phone number empty gives the phone field an error that says to write a number or to choose By email.
- [ ] A good request is replaced, inside the same card, by a confirmation in an element with the status role that was there from the start; its heading takes focus, and it says where the reply goes and that it comes within two working days.
- [ ] The note that this is not an emergency service stands above the form, says to call your local emergency number or go to the nearest emergency department, and holds no phone number and no organisation's name.
- [ ] Send my request is the page's one default (indigo) button, the only round things are the radio buttons, and no text is italic or heavier than weight 500.
- [ ] No color is written as a hex, rgb or Tailwind palette value in the page's code.
- [ ] Every padding, margin and gap in the code you wrote is a step of the spacing scale.
- [ ] Every text element you wrote gets its size from a type-scale class, its own or an inherited one. Visually hidden text is exempt.
- [ ] No file in `components/ui` imports `cn` from `"cn"` directly.
- [ ] Text contrast meets WCAG AA in light and dark mode.
- [ ] The page has one `<h1>`, headings are in order, and every image has alt text.
- [ ] Every interactive element can be reached with the keyboard and shows the focus outline from the rules.
- [ ] At 375px wide the page does not scroll sideways. Wide tables and code blocks scroll inside their own box.
- [ ] The project builds without errors or warnings.

Then tell the user which checks pass, which you could not verify, and anything in this brief you did not do and why.
