# Build a payment form with the Ledger theme

You are building one page in an existing project: a form to send a payment from a business account, with checks on every field, a review step that states the amount in words, and a confirmation that says nothing was sent. 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 Ledger variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/ledger.json` and save https://strictthemes.com/themes/ledger/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/ledger/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:** Instrument Serif for headings, Instrument Sans for body text, Spline Sans Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 64px/400, `text-h1` 42px/400, `text-h2` 28px/400, `text-body` 15px/400, `text-label` 12px/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`), 12px (`3`), 16px (`4`), 24px (`6`), 48px (`12`).
- **Corners:** `rounded-sm` 2px, `rounded-md` 3px, `rounded-xl` 6px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 3px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #0E5A40, on-primary #FFFFFF, secondary #14211B, on-secondary #FFFFFF, tertiary #B98A2F, on-tertiary #14211B, neutral #5A6860, background #F6F4EC, surface #FFFFFF, foreground #14211B, border #D9D6C8, error #B42318.

### How Ledger pages are laid out

- **Page:** cream paper: the outermost element is a `<div>` with `flex min-h-svh flex-col bg-background text-body text-foreground`, holding `<header>`, `<main>` (`flex-1`) and `<footer>`.
- **Container:** the header's row, the content of `<main>` and the footer's line each sit in a centered container `<div>`, `mx-auto w-full max-w-[1200px] px-4 md:px-6`. This width is the one arbitrary value allowed. The `<main>` element itself has `py-6 md:py-12`; the container inside it carries the rhythm below.
- **Today:** the sample's today is Friday 27 November 2026, a fixed date in code, never the device clock. A limit given as a date includes that day.
- **Dates:** a date that stands on its own (a value, a small figure, a list row) is `27 Nov 2026`, in a `<time>` with `dateTime="2026-11-27"`; in a table's Date column it drops the year, `27 Nov`, still a `<time>` with the full `dateTime`. Inside a sentence a date is written out, `27 November 2026` (or `30 November` in a sentence about this month), and is a `<time>` only where this brief says. A date range in a label or a cell ("2–8 Nov") is plain text, unless this brief asks for two `<time>` elements.
- **Money:** pounds with two decimals and a thousands comma: `£1,240.50`. Money out has a real minus sign, U+2212 (`−£84.20`), never a hyphen. Money in has a plus sign (`+£1,240.50`) where it stands beside money out; a balance has no sign. A sum of exactly zero is `£0.00` with no sign and no color; its word stays ("In £0.00"). A brief that rounds to whole pounds says so.
- **Links:** the product's name links to `/`. The nav links, in this order, are Overview (`/`), Transactions (`/transactions`), Invoices (`/invoice`), Payments (`/payment`) and Reports (`/report`), the same in both navs; this brief says which is current, and that link keeps its own path. Any other link without a named destination is `#`. A text link is `text-primary underline underline-offset-4 hover:text-foreground` with `transition-colors duration-150`, and inherits its size. A link in a sheet's heading row is a label in `text-primary` with the same underline and hover.
- **Vertical rhythm:** every vertical distance between siblings comes from a `gap-*` on their parent, a `flex flex-col` or a grid. Never use `space-y-*`. Rows and cells use padding. The only margins are the Confirmations note's. The container inside `<main>` is `flex flex-col gap-6`: the page header, then each row of sheets, and each sheet this brief puts in the container directly, full width; on a page this brief divides into sections it is `gap-12`. The other steps: `gap-1` between a label and the value under it; `gap-2` between a field and its helper text and error, and between buttons in a row; `gap-3` between a label and its field or button group (`DESIGN.md`'s 12px), and between a sheet's heading row and its content; `gap-4` between the fields of a form and between paragraphs; `gap-6` between sheets in a column and between groups inside a sheet.
- **Grid:** where this brief puts sheets side by side, they are items of a 12-column grid, `grid grid-cols-1 gap-6 lg:grid-cols-12`, single column below `lg`. A column of several sheets is `flex flex-col gap-6`, and its last sheet has `flex-1`, so the columns of a row end level.
- **Header:** a full-width `<header>` with `border-b border-border` and no fill. Its container is the row, `flex items-center gap-6 py-4`: the product's name in `font-heading text-h2 text-foreground`, no underline; then a `<nav>` with the `aria-label` "Main" and `hidden md:block`, holding a `<ul>` with `flex gap-6` of the nav links; then, at the right end (`ml-auto`), the business's name as a small figure, `hidden md:block`; and the "Menu" button of the note on menus below. A nav link is a label: the current page's link is `text-foreground` with `aria-current="page"` and `underline underline-offset-4`, the others `text-muted-foreground hover:text-foreground`.
- **Footer:** a `<footer>` with a 1px top border across the full width. Its container has `py-6` and holds one small figure: "Fairhold · A demo. No money moves. · 2026".
- **Page header:** a row that is not a sheet: `flex flex-wrap items-end justify-between gap-4`. On the left a stack with `gap-1`: the label or small figure this brief gives, if any, then the `<h1>` in `text-h1`. On the right the page's actions, `flex flex-wrap gap-2`.
- **Headings:** a serif `<h2>`, where this brief asks for one, is `text-h2`. Headings and key figures are set in Instrument Serif, which has one weight: never put `font-bold`, `font-semibold` or `font-medium` on a heading, a key figure or a serif total, and where a stock component ships `font-medium` around serif text, add `font-normal`. Sentence case.
- **Sheets:** a sheet is a plain element with `rounded-xl border border-border bg-card p-6`, not the shadcn `Card`. Never put a sheet inside a sheet: divide its parts with 1px rules (`border-t border-border`). A sheet is a `flex flex-col gap-3` when it holds a heading row and content.
- **Sheet heading:** a sheet opens with a heading row, unless this brief says it has none: `flex min-h-8 flex-wrap items-center justify-between gap-3`. It holds a label heading, an `<h2>` (or `<h3>`, to keep heading order) in `font-mono text-label uppercase text-muted-foreground`, and at its right end anything that belongs to the whole sheet: a link, a badge, a count, a filter.
- **Sheets with rows:** a sheet that holds a table or a list of rows has no padding of its own, no gap, and `overflow-hidden`. Its heading row is one element: the heading row's classes plus `px-6 pt-6 pb-3 border-b border-border`, with no wrapper around it. Each row of a list has `px-6 py-3` and a 1px top border, except the first.
- **Labels:** sheet headings, table headers, field labels, nav links, legends, and button and badge text are labels: `font-mono text-label uppercase`, a short fragment, in `text-muted-foreground` unless a note or this brief gives another color. Button and badge text keep their component's color. Helper text and error lines share the label's font and size but are sentences and not uppercased: they are not labels.
- **Small figures:** anything this brief calls a small figure (a date, a reference, a count, a name, helper text) is `font-mono text-label tabular-nums text-muted-foreground`, without `uppercase`. A `text-foreground` in this brief replaces the muted color.
- **Numbers:** an amount, a date, an account number or a reference that stands on its own (a cell, a value, a list row) is `font-mono tabular-nums`; inside a sentence it stays in the body font. In a table, amounts and their column's header are right-aligned (`text-right`). A date that stands on its own is a `<time>` with a `dateTime`.
- **Key figures:** a balance or a total that a sheet is about is `font-heading tabular-nums text-foreground`, in `text-h1`, or `text-h1 xl:text-display` where this brief says display. Its label sits above it with `gap-1`. It has no color of its own: a figure for money out says "out" in its label.
- **Signed amounts:** money in or out in a row or a cell is one element, `inline-flex items-baseline justify-end gap-2 whitespace-nowrap`: the word first, a label: "In" or "Out", in `text-muted-foreground`, then the figure in `font-mono tabular-nums`, `text-primary` with a plus sign for money in, `text-destructive` with a minus sign for money out. The word comes first, so the digits keep the column's right edge. Never show in or out by color alone. Green and red mean in and out and nothing else, never decoration.
- **Measure:** a paragraph of body text (`text-body`) is `text-foreground`, never muted, and at most `max-w-lg` wide (about 70 characters a line). A small figure stays muted even when it is a sentence in a `<p>`: it is not a paragraph in this sense.
- **Buttons:** the shadcn `Button` with `className="px-4 font-mono text-label uppercase"`, its stock height and corner; add it with `npx shadcn@latest add button` if missing. The default variant (pine green) is for the one main action of a view: at most one enabled default button at a time. Other actions are `variant="secondary"` (green-black ink) or, for quiet ones, `variant="outline"`. On a page with a `secondary` button, replace the `secondary` variant's stock `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]` with `hover:bg-secondary/80` in `components/ui/button.tsx`, once; add nothing to the buttons: the stock hover does not change this theme's secondary color. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A button that keeps the focus when its own click disables it (Previous and Next, "Approve", "Mark as paid") stays on the page, disabled, with `focusableWhenDisabled`. A button whose click moves focus elsewhere, or that is disabled before anyone clicks it, uses the plain `disabled` prop. With `focusableWhenDisabled`, Base UI sets `aria-disabled` and `data-disabled`, not `disabled`, so the stock `disabled:` classes do nothing: on a page that uses the prop, and only there, add `data-disabled:pointer-events-none data-disabled:opacity-50` beside them in `components/ui/button.tsx`. A link styled as a button is an `<a>` with `cn(buttonVariants({ variant }), "…")` and the same classes: `buttonVariants` alone does not merge classes.
- **Button groups:** a set of buttons where one is chosen (a filter) is a `role="group"` with `flex flex-wrap gap-1` and an `aria-label`. The chosen button has `aria-pressed="true"` and `variant="secondary"`; the rest have `aria-pressed="false"` and `variant="outline"`.
- **Badges:** a badge marks something that waits on someone: "Due", "To approve". It is the shadcn `Badge` (`npx shadcn@latest add badge`) in the `outline` variant with `className="h-auto rounded-sm border-transparent bg-tertiary px-2 py-1 font-mono text-label uppercase text-tertiary-foreground"`. The corner must be in this `className`, or the stock pill wins. One or two words, no icon. Brass is a fill only: never write `text-tertiary`.
- **States:** a state that waits on no one is a status mark and a word: `inline-flex items-center gap-2`. The mark is a small square, `size-2 shrink-0 rounded-sm` with `aria-hidden="true"`: `bg-primary` for settled (Paid, Cleared, Approved), `bg-destructive` for failed or late (Overdue, Declined), `bg-muted-foreground` for neutral (Draft, Sent, Scheduled). The word is `text-body text-foreground`. Never show a state by color alone.
- **Inputs:** the shadcn `Input` (`npx shadcn@latest add input`) with `className="text-body md:text-body"`, its stock height and corner. In `components/ui/input.tsx`, replace `bg-transparent` and `dark:bg-input/30` with `bg-card`, to match the selects; `file:bg-transparent` and the `disabled:` fills stay. An amount field is also `font-mono tabular-nums text-right`. A field is a `flex flex-col gap-3`: a visible `<label>` with `block`, styled as a label and tied to the field with `htmlFor`; then a stack with `gap-2` holding the field, any helper text (a small figure) and the error line when there is one. Name the helper text in the field's `aria-describedby`.
- **Selects:** a native `<select>` with `h-8 w-full rounded-lg border border-input bg-card px-2 text-body text-foreground aria-invalid:border-destructive` and the focus outline from the rules (`focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`), so an error shows as on an `Input`. Set a width on an element around it, not on the `<select>`. Same label and stack as any field.
- **Errors:** an error line is `flex items-start gap-2 font-mono text-label text-destructive`, not uppercased, holding the `CircleAlert` icon (`shrink-0`; it is as tall as the line, so `items-start` lines it up) and a sentence that says what to do. The field gets `aria-invalid` and names the line in its `aria-describedby`. Fields are checked in code on submit, with `noValidate` on the `<form>`; focus moves to the first field with an error. An error line is removed when its own field changes (on `input` or `change`); an error that depends on a second field is also removed when that field changes. Every other error stays until the next submit.
- **Confirmations:** a message after an action goes into a `role="status"` element that is on the page from the start, empty, or it may not be announced. It is never hidden, has no classes that draw or take space, and is never an item of a `gap-*` stack or row: put it in a plain `<div>` with the element above it. The message inside it, rendered only when there is one, carries the look and the space, such as `mt-3`. Buttons or links that follow a message sit after the status element in that `<div>`, not inside it, with their own top margin. Where a message replaces a form, the form and the status element share that `<div>`. Where the message replaces the control that had focus, give its first element `tabIndex={-1}` and move focus to it.
- **Tables:** the shadcn `Table` (`npx shadcn@latest add table`) with `text-body`. Header cells are labels in `text-muted-foreground`, keep their stock `h-10` and `whitespace-nowrap` (so a header is one short line) and get `px-4`. Body and footer cells get `px-4 py-2`; in a sheet with rows the first and last cell of every row, header and footer rows included, also get `first:pl-6 last:pr-6`. Cells that hold a sentence get `whitespace-normal`. Body rows keep their 1px borders and the stock `TableRow` hover fill, also where a row does nothing. No `<caption>`, which would scroll away: the table's visible name is the heading above its box (the sheet's heading row or the section's `<h2>`), and the `label` prop repeats its words; where this brief gives a table no heading, the `label` alone names it. A wide table scrolls sideways in its own box: edit `components/ui/table.tsx` to give its scroll box `tabIndex={0}`, `role="region"`, an `aria-label` from a required `label` prop, and the focus outline drawn inside (`focus-visible:-outline-offset-2`).
- **Totals:** a total row sits in a `<tfoot>`. The stock `TableFooter` ships a fill and `font-medium`: give it `bg-transparent font-normal`, and each of its `TableRow`s `hover:bg-transparent`. The total row's cells (not the `<tr>`) get `border-t-4 border-double border-foreground`. Its label and amounts are `font-heading text-h2 tabular-nums`.
- **Icons:** from `lucide-react`, with `size-4` and `strokeWidth={1.5}`, only where this brief or these notes name one, with `aria-hidden="true"` beside a word. Never emoji.
- **Charts:** drawn with plain elements, with square corners. Money in is `bg-primary` and money out `bg-destructive`, always in that order, with a legend in words; grid lines are 1px `border-border`; axis values are small figures. The drawing has `aria-hidden="true"`, and every chart has a text alternative that gives the numbers.
- **Corners:** `rounded-sm` (2px) for badges and status marks, `rounded-lg` (3px, what they ship with) for buttons, inputs and selects, `rounded-xl` (6px) for sheets. No full rounds and no pills.
- **Surfaces:** flat. No shadows, blur or gradients in the code you write.
- **Menus on small screens:** below `md` the nav links go behind a "Menu" button (`variant="outline"`, `md:hidden`) with `aria-expanded` and `aria-controls`, at the right end of the header's row (`ml-auto`). It opens a second `<nav>` under the row, inside the `<header>`, also named "Main" because only one of the two is ever shown: it has `md:hidden`, the `hidden` attribute while closed, the `id` that `aria-controls` names and a full-width 1px top border; in its container, a `<ul>` with `py-2`, each link a block with `py-2`. Do not use a `Sheet` or a `Dialog`.
- **Motion:** only colors transition in the code you write; stock components keep their motion.

## The page

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

Sample content, if the user gave none: "Fairhold", a banking and invoicing service for small businesses, used by "Hollis & Vane", a two-person print studio. Two accounts to pay from: the current account (ending 4417, £48,213.56 available) and the tax reserve (ending 8830, £13,690.71 available). Four saved payees, each with a sort code and an account number: Copperleaf Paper Mill, Brightwater Inks, Kestle Property Lettings and Marlowe Van Leasing.

This page uses the stock `button` and `input`; add any the project lacks with `npx shadcn@latest add button input`.

Build these parts in order; the footer is as the layout notes describe it.

### 1. Header

The product's name, five nav links (Overview, Transactions, Invoices, Payments, Reports) with Payments as the current page, and at the right end the business's name as a small figure, hidden below `md`, where the notes' "Menu" button takes the right end. Nav links go to the notes' paths.

### 2. Page header

Above the heading, the label "Payments". The `<h1>` "Make a payment". No actions.

### 3. Payment sheet

A grid row: the payment sheet spans eight columns from `lg` up. Heading: `Payment details`; `Check the payment` on the review step; `Payment recorded` on the last step. At the right end of the heading row, the step as a small figure: `Step 1 of 3 · Details`, then `Step 2 of 3 · Review` and `Step 3 of 3 · Done`.

Under the heading row, one plain `<div>` holds the form or the review, then the confirmation element, on the page from the start and empty, then, after a payment, the confirmation's row of buttons.

**The form.** A `<form>` with `noValidate`, a stack with `gap-4`; every field is at most `max-w-md` wide.

- **From:** a `<select>` "From account" with the two accounts, each option reading `Current account · ending 4417`. Helper text: the chosen account's available balance, `£48,213.56 available`.
- **Payee:** a `<select>` "Payee" whose first option, "Choose a payee", has an empty value, then the four payees. Helper text: the chosen payee's details, `Sort code 40-11-62 · Account 70119382`, or nothing while none is chosen.
- **Amount:** a field "Amount": the `£` sign as a small figure in `text-foreground`, then the `Input` with `inputMode="decimal"`, side by side in a row with `flex w-48 items-center gap-2`; the label, helper and error lines keep the form's width. Helper text: "Up to £10,000.00 a payment."
- **Reference:** an `Input` "Reference". Helper text: "Up to 18 letters, numbers and spaces. The payee sees this."
- **Date:** an `Input` with `type="date"` "Pay on", set to today, 27 November 2026. Helper text: "Today, or a later day up to and including 26 January 2027."
- **Buttons:** a row with `flex flex-wrap gap-2`: the default button "Review payment" with `type="submit"`, and an `outline` link styled as a button, "Cancel", to `/`.

**The review.** It takes the form's place in the wrapper. A stack with `gap-6`:

- A stack with `gap-1`: the label "You are sending", the amount as a key figure in `text-h1`, `£1,240.50`, and under it the amount in words, a `<p>` in `text-body text-foreground`. Give this `<p>` `tabIndex={-1}` and move focus to it when the review appears.
- A `<dl>` of five rows, each `flex items-baseline justify-between gap-3 py-2`, a 1px top border on all but the first; the `<dt>` is a label in `text-muted-foreground`, the `<dd>` has `text-right`. From (the chosen option's text in `text-body`), To (a stack with `gap-1 items-end`: the payee in `text-body`, then its sort code and account number as a small figure), Reference (`font-mono`), Pay on (`font-mono tabular-nums`: a `<time>`, `27 Nov 2026`, with "Today, " before it when it is today) and Balance after (`font-mono tabular-nums`, the available balance less the amount).
- In `text-body`: "Check the payee's details. A payment cannot be called back once it is sent."
- A row with `flex flex-wrap gap-2`: the default button "Send payment" and an `outline` button "Change details".

**The confirmation.** Inside the confirmation element, rendered only after a payment, a stack with `gap-3` and no top margin (here, instead of the Confirmations note's `mt-3`: nothing stands above it in the wrapper then, and the sheet's `gap-3` spaces it from the heading row): a `<p>` in `font-heading text-h2 tabular-nums`, the amount and the payee, "£1,240.50 to Copperleaf Paper Mill"; one sentence in `text-body`, "From the current account, on 27 November 2026." ("From the tax reserve, on …" for the other account), with the Pay on date written out; and one sentence in `text-body`: "This is a demo: no money left the account and nothing was sent anywhere." After the confirmation element, not inside it, a row with `mt-6 flex flex-wrap items-center gap-2` holding a `secondary` button "Make another payment" and a text link "Back to overview" (`/`).

### 4. Side column

Four columns from `lg` up, full width below: a sheet with rows. Heading: `Good to know`. Two rows in `text-body`: payments for today go out within two hours, later ones at 06:00 on their day; the limit is £10,000.00 a payment.

### Amounts in words

Pounds, then pence; only the first letter capitalised; "and" before the tens and units of each hundreds group; a comma after "thousand" when hundreds follow; hyphenated tens and units; "pound" and "penny" for one; no "and zero pence". Examples:

- `£1,240.50`: "One thousand, two hundred and forty pounds and fifty pence"
- `£1,005.00`: "One thousand and five pounds"; `£300.01`: "Three hundred pounds and one penny"; `£1.00`: "One pound"; `£0.75`: "Seventy-five pence"

### Behavior

- "Review payment" checks every field in code. Each error, with its text:
  - Payee: "Choose who to pay."
  - Amount empty: "Enter an amount." Anything but digits with an optional point and one or two digits after it (`240.5` is one; `240.` and `.5` are not), or zero: "Enter an amount in pounds and pence, such as 240.50." Check the balance before the limit. Above the chosen account's balance: "That is more than the £13,690.71 available in this account.", with that account's figure. Within the balance but above £10,000.00: "The most you can send in one payment is £10,000.00."
  - Reference empty: "Enter a reference the payee will recognise." Over 18 characters, or anything but letters, numbers and spaces: "Use up to 18 letters, numbers and spaces."
  - Pay on empty: "Enter a date." Before today: "Choose today or a later date." After 26 January 2027, which is still allowed: "Choose a date up to 26 January 2027."
- The amount ignores spaces, commas and a leading `£`. The field keeps what was typed, also after "Change details"; the review, the confirmation and the balance after show it with two decimals and a thousands comma (`1240.5` reads `£1,240.50`). The reference is trimmed at both ends before it is checked and shown, so a reference of only spaces is empty; spaces inside it count toward the 18. The Amount's error is also removed when From changes.
- With no errors, the review replaces the form.
- "Change details" brings back the form, values kept, and focuses From.
- "Send payment" removes the review, fills the confirmation element and moves focus to the sheet's `<h2>`, now "Payment recorded", which has `tabIndex={-1}` (here, instead of the Confirmations note's focus on the message's first element, so that the message is not read twice). The balances do not change.
- "Make another payment" empties the confirmation, removes its row of buttons, brings back an empty form (today's date, the current account) and moves focus to From.

## 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`, `3`, `4`, `6`, `12`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-5` or `gap-10`. `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. This theme has no full rounds: do not use `rounded-full`, and give a small mark a corner under half its height or none, because a box whose corner is half its height or more is a full round however it is written.
- **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 right-align amounts and use tabular figures.
- Do show a negative amount with a minus sign and a word, not by color alone.
- Do keep the serif in its one weight and let size set the hierarchy.
- Don't use green and red as decoration: here they mean in and out.
- Don't use gradients, glows or rounded pills.
- 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.

- [ ] Sending the form with empty or wrong fields shows an error in words with an icon under each field, tied with `aria-describedby`, and moves focus to the first field with an error.
- [ ] The review step states the amount in figures and in words (£1,240.50 is "One thousand, two hundred and forty pounds and fifty pence"), and "Change details" goes back with every value kept.
- [ ] After "Send payment" a confirmation is announced from a `role="status"` element that was on the page from the start, it says that nothing was sent, and focus moves to it.
- [ ] An amount above the account's available balance or above £10,000.00 is refused with its own error message.
- [ ] 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.
