# Build a handbook chapter with the Recital theme

You are building one page in an existing project: one chapter of a guide or handbook, read from start to finish, with a list of its sections, a table, an aside and links to the chapters before and after. 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 Recital variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/recital.json` and save https://strictthemes.com/themes/recital/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/recital/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:** Fraunces for headings, Source Sans 3 for body text, Source Code Pro for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 60px/600, `text-h1` 40px/600, `text-h2` 28px/500, `text-body` 18px/400, `text-label` 13px/500.
- **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`), 24px (`6`), 48px (`12`), 96px (`24`).
- **Corners:** `rounded-sm` 3px, `rounded-md` 6px, `rounded-xl` 12px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 6px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #7B2D26, on-primary #FFF9F0, secondary #2B2622, on-secondary #FFF9F0, tertiary #B8892B, on-tertiary #1F1B18, neutral #6F665E, background #FBF7F0, surface #F3ECE1, foreground #1F1B18, border #DDD3C4, error #A3261C.

### How Recital pages are laid out

- **Container:** one `<div>` wraps the whole page and is the container: `mx-auto max-w-3xl` (768px) with `px-4`, and `px-6` from `md` up, plus `text-body`, which every paragraph, list and card inherits: they need no size class of their own. Inside it sit the `<header>`, a `<main>` holding everything between the two, and the `<footer>`. The header, the footer and whatever this brief calls wide (a figure, a table, a row of numbers) use its full width.
- **Reading column:** all other content sits in a reading column inside the container: `mx-auto max-w-lg` (512px, 60 to 70 characters a line in this font). Headings, paragraphs, lists, block quotes, cards and forms are in it. One column only: no sidebar, and no text set in two columns.
- **Wide blocks between text:** the page is a stack of blocks inside the container, each one either a reading column or a wide block. Where a wide block comes in the middle of the text, end the reading column before it and start a new one after it. The wide block carries the `my-12`; the column after it has no margin of its own, and its first element keeps its usual top margin, which collapses into the block's `mb-12`. Do not pull an element out of the column with a negative margin.
- **Alignment:** text is left-aligned. Nothing is centered or justified, headings and short lines included. Only the two columns themselves are centered on the page.
- **Vertical rhythm:** written as a top margin on each element, never as `space-y-*` on the column: `space-y-*` also puts its margin under every heading. A paragraph or a list that follows text has `mt-6`. An `<h2>` has `mt-12`, and the text under a heading has `mt-4`. A block quote, a figure, a card, a table or a block of code has `my-12`. A label and the thing it labels are `mt-2` apart, where this brief gives no other margin. A new part of the page starts with `mt-24`, where this brief says so. Where a part begins "In the reading column, with `mt-…`" (or "Wide, with `mt-…`"), that margin goes on the part's own reading column `<div>` (or wide block), and the part's first element takes no top margin of its own: an `<h2>` that opens it has no `mt-12`, and a card that opens it has `mb-12` in place of `my-12`. Where this brief gives a block its own top margin, it replaces the top half of that block's `my-12`. A link or a button that stands on a line of its own sits in a block element (a `<p>` for a link, a `<div>` for a button), and the margin this brief gives it goes on that element. These margins rely on collapsing: keep the columns and the parts in them in normal block flow, not flex or grid. A row inside a part that this brief lays out with `flex` or `grid` (the header, a list item set as one row) is the exception, and the margins of its children do not collapse.
- **Headings:** the `<h1>` is `text-h1`, and `text-display` only where this brief says so. An `<h2>` is `text-h2`. An `<h3>` is `text-body font-semibold`, in the heading font it gets on its own, unless this brief gives it another size. Sentence case, no full stop. A label heading, where this brief names one, is an `<h2>` set as a label: `font-mono text-label text-muted-foreground`, with no top margin when it opens its part or its card.
- **Standfirst:** the sentence or two under a title that sum the piece up: a `<p>` in `font-heading text-h2 font-normal italic`.
- **Labels:** dates, bylines, reading times, captions, table headers, form labels, and button and badge text are labels: `font-mono text-label`. Write them in sentence case. Nothing is uppercased, in the text or with a class. A label on its own line is a block element such as a `<p>`. Parts of a label line are joined with ` · `.
- **Dates:** written day, month in full, year: `12 March 2026`, and `12 March` where the year stands in a heading above the entry. Every date in a label, a heading or a list item of its own is a `<time>` with a `dateTime` (`2026-03-12`, or `2019` for a year alone), unless this brief says it is plain text. A date or a year inside a sentence of running text, and a copyright year, are plain text.
- **Text color:** headings, body text and form labels are `text-foreground`. Other labels are `text-muted-foreground`. A button's text color comes from its variant. Paragraphs are never muted.
- **Emphasis:** `<em>` for emphasis, and nothing else: no bold in running text, no color, no underline. No small caps either. The theme loads real italics for the heading and the body font.
- **Links:** a link in running text is `text-primary underline decoration-1 underline-offset-4 hover:text-foreground` and inherits its size. A link in the header, the footer or a list of titles is `text-foreground decoration-1 underline-offset-4 hover:underline`. The link for the current page has `aria-current="page"` and `underline`; mark one only where this brief names the current page. Links get `transition-colors duration-150`.
- **Header:** the `<header>` is itself the row, with `py-6` and no border: `flex flex-col items-start gap-y-2 md:flex-row md:items-baseline md:justify-between`. On the left the publication's name, a link to `/` in `font-heading text-h2`, styled as a header link and never marked as the current page. On the right a `<nav>` with the `aria-label` "Main" holding the links in `text-body` with `gap-6` between them. Below `md` the links sit on a second line under the name. No "Menu" button.
- **Footer:** a `<footer>` in the container with `mt-24`. Inside it one element with a 1px top border and `py-12` holds the publication's name as plain text in `font-heading text-h2`, one sentence about it with `mt-2`, and a label line with the copyright with `mt-6`.
- **Buttons:** the shadcn `Button` with `className="h-auto rounded-md px-6 py-2 font-mono text-label"`. The default variant (oxblood) is for the page's one main action. Any other button is `variant="secondary"` (ink). Its stock hover color cannot be told from its resting one in this theme: in `components/ui/button.tsx`, replace the `secondary` variant's `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80`, once, and add no hover class on the page. The buttons of a group that are not the chosen one are `variant="outline"`. 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 or jumps within this one is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "…")`, because `buttonVariants` alone does not merge classes. Button text is sentence case, at most three words.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="h-auto rounded-md border-muted-foreground px-4 py-2 text-body md:text-body"`. The stock `border-input` is about 1.4:1 against the page, too faint to find the field; `border-muted-foreground` reaches 3:1 in both modes. The stock `focus-visible:border-ring` and `aria-invalid:border-destructive` stay. They keep the fill they ship with, in dark mode too. Every field has a visible `<label>` above it with `block`, tied to it with `htmlFor`, and the field has `mt-2`.
- **Errors:** an error is a line under its field with `mt-2`: `flex items-start gap-2 font-mono text-label text-destructive`, holding the `CircleAlert` icon (`shrink-0`) and a sentence that says what to do. The field gets `aria-invalid` and points to the line with `aria-describedby`. Fields are checked in code when the form is sent, with `noValidate` on the `<form>`, and focus moves to the first field with an error. An error line is removed when its own field changes (on `input`); the errors of the other fields stay until the next submit.
- **Confirmations:** a message that replaces a form goes into an element with `role="status"` that is on the page from the start, empty: a status element that arrives together with its text is often not announced. The form and the status element share one wrapper, and the wrapper carries the top margin, so the message stands where the form stood. Never hide the empty status element. Give the message's first element `tabIndex={-1}` and move focus to it, because the button that had focus is gone.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-sm bg-tertiary px-2 py-1 font-mono text-label text-tertiary-foreground"`. One or two words. At most one badge on a page.
- **Cards:** a card is a plain element (an `<aside>` for a note beside the text) with `rounded-xl border bg-card p-6`. Inside it, a label comes first, with no top margin: a `<p>`, not a heading, unless this brief calls it a label heading. What follows it has `mt-2` unless this brief gives another margin. Use one only where this brief asks for it. Never put a card inside a card, and do not use the shadcn `Card`: it ships its own padding and a ring.
- **Block quotes:** a `<blockquote>` with `border-l-2 border-primary pl-6`. The quote is a `<p>` inside it in `font-heading text-h2 font-normal italic`, with no quotation marks. Who said it is a `<footer>` inside the `<blockquote>` with `mt-4`, a label with `not-italic`.
- **Figures:** a `<figure>` is wide. Its panel is `rounded-xl border bg-card p-6`, drawn from plain elements with `bg-foreground`, `bg-background` and borders: no image files, no primary color, no gradient. The `<figcaption>` is a label under the panel with `mt-2`.
- **Lists:** a list in running text is `list-disc` or `list-decimal` with `pl-6`, and `mt-2` between its items. A list of entries or titles, where this brief gives each item a top border, is a `<ul>` (or the list element this brief names) with no markers, no padding and no space between items, and no line under its last item.
- **Rules:** every line is 1px and `border-border`, except the block quote's rule.
- **Corners:** `rounded-sm` for badges, `rounded-md` for buttons and inputs, `rounded-xl` for cards and figures, `rounded-full` only for avatars.
- **Surfaces:** flat. No shadows, no blur, no gradients in the code you write. A stock component that floats over the page (a menu, a dialog) keeps the one soft shadow it ships with.
- **Icons:** from the project's icon set, with `size-4`, `strokeWidth={1.5}` and `aria-hidden="true"`. Use them only where this brief or these notes name one.
- **Small text:** a bare `<sup>` or `<small>` gets a size that is not in the type scale. Give it `text-label`.
- **Motion:** in the code you write, the only transition is color. Nothing moves on scroll. Stock components keep the motion they ship with.

## The page

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

Sample chapter, if the user gave none: chapter 3, "Writing things down", of "A Handbook for Small Teams", a guide for teams of five to fifteen people. The chapter argues that a decision is not made until it is written where the team can find it, and shows how. Write 700 to 900 words of running text yourself, plain and specific, in the paragraphs of four `<h2>` sections, the sentence that introduces the steps included; the standfirst, the aside, "In short", the table, the steps, the template and the quote come on top of that.

Build these parts in order. An `<article>` wraps the chapter opening, the list of sections, the body and "In short".

### 1. Header

The handbook's title in place of a publication's name, and two nav links, in this order: Contents (`/contents`), About (`/about`). None is marked as the current page. A long title may wrap on a phone.

### 2. Chapter opening

In the reading column, with `pt-24`:

- A label: `Chapter 3 of 9`.
- With `mt-4`, the `<h1>`: `text-display` from `md` up, `text-h1` below.
- With `mt-6`, a standfirst: two sentences.

### 3. In this chapter

In the reading column, with `mt-12`: a `<nav>` named by its label heading: `aria-labelledby` set to the heading's `id`, and no `aria-label`, so the name is read once. It has a 1px top border and a 1px bottom border and `py-6`. Inside it a label heading "In this chapter", then with `mt-4` (here, instead of the notes' `mt-2` under a label) a numbered list (`list-decimal pl-6`, `mt-2` between items) with one link for each of the body's four section headings, styled as links in a list of titles.

### 4. Body

In the reading column, except the table, with `mt-12`. The body must include, each once:

- **Four `<h2>` headings**, each with an `id` and `scroll-mt-6`. The first one has no `mt-12`: the body's own margin is enough.
- **A numbered list** of four steps, one sentence each, after a sentence that introduces it.
- **A table**, wide, between two reading columns: a small comparison with three columns and four rows, for example "What to write where" with the columns Kind of note, Where it lives and Who reads it. Use the shadcn `Table`. A `<div>` with `my-12` holds the table's name and the table: first the name as a label `<p>` ("What to write where"), then the `Table` inside a `<div>` with `mt-2`. No `<caption>`: inside the scroll box it would scroll sideways with the table. The `Table` itself gets `className="min-w-lg text-body"`: `text-body` because it ships a smaller size, and `min-w-lg` so that on a phone it scrolls sideways in its box instead of squeezing. Header cells are labels in `text-muted-foreground` with `h-auto`; header and body cells alike have `px-4 py-2 align-top whitespace-normal`, and the first and last cell of a row have `first:pl-0 last:pr-0`. Rows are divided by 1px borders and do not change on hover (`hover:bg-transparent` on every row, the header row included), because they do nothing. The scroll box is reachable with the keyboard: in `components/ui/table.tsx`, give `Table` a required `label: string` prop, and put `tabIndex={0}`, `role="region"`, `aria-label={label}` and the focus outline from the rules (`focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`) on its scroll box `<div>` always, also where the table fits. Pass the name as `label`.
- **An aside**: a card with a label ("Try this") and two or three sentences that give the reader one thing to do this week.
- **A template**: a block the reader can copy, for example a decision record with five lines (Decision, Date, Who decided, Why, When to look again), each filled in with an example long enough that the block is wider than a phone. It stays in the reading column, 512px at most, and scrolls inside itself on a desktop as well wherever a line is longer than that. The dates in it are plain text, not `<time>` elements. The `<pre>` is the panel: `rounded-xl border bg-card p-6 font-mono text-label text-foreground`. It scrolls sideways inside itself (`overflow-x-auto`), with `tabIndex={0}`, `role="region"`, an `aria-label` and the same focus outline. A label that names it sits above it; the label and the `<pre>` share a wrapper with `my-12`, and the `<pre>` has `mt-2`.
- **A block quote** of one sentence, with the name of who said it.
- **An emphasis** (`<em>`) in at least two paragraphs.

### 5. In short

In the reading column, with `mt-12`: a card, a `<section>` (not an `<aside>`: it sums up the chapter). The card has `mb-12`, as the notes give a card that opens a part. Inside it a label heading "In short", then with `mt-4` a bulleted list (`list-disc pl-6`) of three sentences that sum up the chapter.

### 6. Previous and next

In the reading column, with `mt-24`: a `<nav>` with the `aria-label` "Chapters", a 1px top border and `pt-6`. Two links side by side in a `grid grid-cols-2 gap-6`, the first with `justify-self-start`, the second right-aligned as a block (`justify-self-end text-right`). Each link is the whole block, with `group text-foreground transition-colors duration-150`, and holds two `<span className="block">`: a label ("Previous" or "Next") in `text-muted-foreground`, and, with `mt-2`, the chapter's number and title in `text-body text-foreground decoration-1 underline-offset-4 group-hover:underline`, e.g. "2. Meetings worth having".

### 7. Footer

The footer from the layout notes, with the handbook's title.

### Behavior

- The links in "In this chapter" jump to their headings.
- Nothing else needs behavior.

## 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`, `6`, `12`, `24`) plus 0 and 1px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-5`. `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 64px 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; a card inside a card; glows and colored shadows.
- **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 give text room: wide margins and tall line height.
- Do use italics for emphasis before reaching for color.
- Don't use pure white, pure black or cool greys.
- Don't use gradients, glows or glass effects.
- Don't put 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 "In this chapter" list has one link for each of the four section headings of the body, and each link jumps to its heading.
- [ ] Body text in the reading column averages 60 to 70 characters a line on a desktop screen.
- [ ] On a phone the table and the template scroll sideways inside their own boxes, which can be reached with the keyboard, and the page itself does not scroll sideways.
- [ ] There is no sidebar, and the links to the previous and the next chapter are in a `<nav>` with a label.
- [ ] 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.
