# Build a therapy approach page with the Solace theme

You are building one page in an existing project: a therapist's page on how sessions work, a long page read in one column, with a short list of contents that links to its sections, what happens before and in a first session, what comes after, one thing a client said set in the italic, what the therapist does not offer and one action. 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: the practice of **Nora Ellingham**, an invented registered psychotherapist who works alone, with adults, one to one, in a garden room in Larkmere, an invented town, and online by video. This page says how she works: a free call of 20 minutes first, then sessions of 50 minutes at £68, once a week at the same time, with a look every sixth session at whether it is still useful. The text below is given word for word, because the page is only as calm as its words: use it as it is, and add no sections of your own.

Build these parts in order.

### 1. Header

The header from the layout notes. "Approach" 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`): `How I work · About 3 minutes to read`.
- With `mt-4`, the `<h1>` in `text-h1 md:text-display`, columns 1–9 (`md:col-span-9`): "How we would work together". The words "work together" are an `<em>` with `italic`.
- 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`: "This page says what a session is like, from the first call to the last meeting. Read as much of it as helps. You can also just ask me."

### 3. The text

A section in the grid, with its `aria-label` "How sessions work" (here, in place of `aria-labelledby`), and two parts. The list of contents comes first in the code, so that on a phone it stands above the text; from `md` up both parts have `md:row-start-1`, so the text takes columns 1–7 and the contents sit at the right of its first lines.

**On this page**, in columns 9–12 (`md:col-start-9 md:col-span-4 md:row-start-1`): a `<nav>` that is a card from the layout notes, with `aria-labelledby` naming its heading and no `aria-label`. It also has `md:sticky md:top-7 md:self-start`, so on a large screen it stays in view while the text is read.

- An `<h2>` with an `id`: "On this page".
- With `mt-4`, an `<ol>` with `list-none`, no padding and `mt-2` between items, holding five links in `text-body`, styled as links in running text, one for each `<h2>` of the text, with the same words.

**The text**, in columns 1–7 (`md:col-span-7 md:row-start-1`), with `mt-12 md:mt-0`: one `<div>` in normal block flow. Every paragraph is a `<p>` in `text-body`, at most `max-w-xl`. Each of the five `<h2>` has an `id` and `scroll-mt-7`; the first has no top margin, the others `mt-12`. The text under a heading starts with `mt-4`, and paragraphs follow each other with `mt-4`. A list, the quotation, and a paragraph that follows a list or the quotation have `mt-7`. In this order, word for word:

**"Before we meet"** (`id="before"`)

- "It starts with a call of 20 minutes, by phone or video. It is free."
- "You can tell me a little about what is going on, and ask me anything. I will say how I work and whether I think I can help. If I am not the right person, I will say so, and where I can I will suggest who might be."
- "Nothing is booked until we have spoken and you have had time to think."

**"The first session"** (`id="first-session"`)

- "A first session is 50 minutes, like every session after it. It costs the same, £68."
- An `<ol>` as a list with hairlines (see the layout notes), three entries. Each `<li>` is also `md:grid md:grid-cols-7 md:items-baseline md:gap-x-7`: in columns 1–2 (`md:col-span-2`) a label line in `text-muted-foreground`; in columns 3–7 (`md:col-span-5`), with `mt-2 md:mt-0`, a `<p>` in `text-body`.
  1. `First 10 minutes`: "We settle in. I explain confidentiality and its few limits, and you can ask about anything on this page."
  2. `Middle 30 minutes`: "You talk about what brings you, in any order. I may ask a question now and then. Silence is allowed."
  3. `Last 10 minutes`: "We stop and look at how it was. I say what I heard, and we each say whether we would like to go on."
- "You do not have to decide in the room. You can write to me a few days later."

**"After that"** (`id="after"`)

- "If we go on, we meet once a week, on the same day and at the same time. A steady rhythm matters more than a fast one."
- "Some people come for eight or ten sessions. Some stay for a year or longer. Every sixth session we take a few minutes to ask whether this is still useful to you."
- "You can end at any point. I ask only that we have one last session to finish properly, rather than stopping by email."

**"What a client said"** (`id="in-their-words"`)

- A `<figure>` holding a `<blockquote>` and a `<figcaption>`. The `<blockquote>` has no border and no padding, and holds one `<p>` in `font-heading text-h2 italic`, with its quotation marks typed: "“I kept waiting to be told what was wrong with me. It took a few weeks to notice that nobody was going to, and that I could put things down in that room that I had carried everywhere else.”" The `<figcaption>`, with `mt-4`, is a caption from the layout notes: "A client, after a year of weekly sessions. Shared with permission, and changed to keep it private."
- "People say many different things about therapy. This is one of them, and it is not a promise of what it would be for you."

**"What I do not offer"** (`id="not-offered"`)

- "It is better to know this before you write."
- A `<ul>` with `list-disc pl-7` and `mt-2` between items, five items in `text-body`:
  1. "Emergency or crisis support. I cannot respond quickly, and I am not available between sessions."
  2. "Diagnosis, medication or medical advice. Your doctor is the right person for those."
  3. "Couples or family therapy. I work with one person at a time."
  4. "Work with anyone under 18."
  5. "Reports for courts, employers or insurers."
- "If you need one of these, tell me when you write. Where I can, I will point you towards someone who offers it."
- "In an emergency, call your local emergency number or go to the nearest emergency department."

The text holds no card, no badge, no picture and no button.

### 4. The one action

A section in the grid, with one card in columns 1–7 (`md:col-span-7`), built as the note The one action says. The section's `aria-labelledby` names the card's `<h2>`.

- The `<h2>`: "If this sounds right for you".
- With `mt-4`, a `<p>` in `text-body`, at most `max-w-xl`: "Write to me with the form. I reply within two working days. The first call is free, and nothing is booked until we have spoken."
- With `mt-7`, a `<div>` holding the default button "Request a first session", an `<a>` to `/first-session`. The page's one default button.

### 5. Footer

The footer from the layout notes.

### On a phone

Every part is one column. The `<h1>` is `text-h1` and wraps over two or three lines. The card On this page comes before the text and is not sticky; the text starts 48px under it. In the first session's list each entry stacks: the label, then its sentences 8px under it. The client's words wrap over eight or nine lines at 24px, which is fine.

### Behavior

- The links in On this page jump to their headings; `scroll-mt-7` keeps each heading clear of the top edge.
- Nothing else has state. Links only.

## 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.

- [ ] The text sits in columns 1–7 from `md` up, every paragraph at most `max-w-xl`, with the list of contents in a card in columns 9–12 and column 8 empty; on a phone the contents come before the text.
- [ ] The five `<h2>` sections of the text each have an `id`, and each link in On this page has the same words as its heading and jumps to it.
- [ ] What happens in a first session is an ordered list of three entries, each led by a label with its minutes, 10, 30 and 10, which add up to the 50 minutes of a session.
- [ ] What a client said is a `<blockquote>` in the heading font's italic inside a `<figure>`, and its caption says it is shared with permission and changed to keep it private.
- [ ] What I do not offer is a list of five things, followed by what to do in an emergency in general words, with no phone number and no named organisation.
- [ ] The page has exactly one default (indigo) button, Request a first session; nothing is bold, and the only italics are the phrase in the `<h1>` and the client's words.
- [ ] 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.
