# Build a record detail with the Avion theme

You are building one page in an existing project: the detail screen of one record in an admin tool, with its state, its progress, a timeline, a table of items, its properties and notes. 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 Avion variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/avion.json` and save https://strictthemes.com/themes/avion/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/avion/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:** Red Hat Display for headings, Red Hat Text for body text, Red Hat Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 52px/800, `text-h1` 34px/700, `text-h2` 22px/700, `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`), 20px (`5`), 32px (`8`), 56px (`14`).
- **Corners:** `rounded-sm` 4px, `rounded-md` 8px, `rounded-xl` 14px (the `lg` corner in `DESIGN.md`). `rounded-lg` is 8px as well: it is shadcn's name for the base corner, the one its stock buttons and inputs use.
- **Colors (light mode):** primary #085F66, on-primary #FFFFFF, secondary #16242E, on-secondary #FFFFFF, tertiary #F2A900, on-tertiary #16242E, neutral #5B6B76, background #F3F6F8, surface #FFFFFF, foreground #16242E, border #D5DDE3, error #C2362B, success #1E7B4B.

### How Avion pages are laid out

- **Page and panels:** the page is `bg-background`, a cool grey. Content sits on it in panels. A panel is a plain element with `rounded-xl border bg-card p-5`; do not use the shadcn `Card`, which ships its own padding and a ring. Never put a panel inside a panel: divide a panel's parts with 1px borders instead.
- **Top bar:** the first thing on the page is a `<header>` that runs the full width, `border-b bg-card`, with one row inside the container, `flex items-center py-3`: the product's name on the left, a link to `/` in `font-heading text-h2`; then, `gap-8` from it, a `<nav>` with the `aria-label` "Main" holding the nav links as labels with `gap-5`; then the bar's actions at the right end (`ml-auto`). For the sample product, the nav links, in this order, are Overview (`/`), Shipments (`/shipments`), Depots (`/depots`), Reports (`/reports`) and Settings (`/settings`); this brief says which of them the page shows and which is current. The link for the current page keeps its address and is `text-foreground` with `aria-current="page"`; the others are `text-muted-foreground hover:text-foreground`.
- **Container:** the top bar's row and the main content sit in a centered container, `mx-auto max-w-[1280px]`, with `px-3`, and `px-5` from `md` up. This width is the one arbitrary value allowed, unless this brief names another. Main content has `py-5`.
- **Grid:** main content is a 12-column grid with `gap-5`, a single column below `md`. The page header and every panel are items of this grid: the page header spans all twelve columns, and each panel spans the columns this brief gives it. Panels in one row stretch to the same height, unless this brief says otherwise. "Full width below `lg`" means `md:col-span-12` from `md` (the single column below `md` needs no class), so a panel of three columns from `lg` is `md:col-span-12 lg:col-span-3`. Where a grid item is a column of several panels, the column is `flex flex-col gap-5` and its last panel has `flex-1`, so that the columns of a row end on one line.
- **Page header:** main content opens with a row that is not a panel: `flex flex-wrap items-center justify-between gap-3`, with the `<h1>` in `text-h1` on the left and the page's actions on the right, `gap-2` between the actions. The row wraps where it has to; it has no breakpoint of its own.
- **Panel heading:** every panel opens with a heading row, unless this brief says a panel has none: `flex min-h-7 flex-wrap items-center justify-between gap-3`. It holds a short label heading, an `<h2>` in `font-mono text-label uppercase text-muted-foreground`, and at its right end any control that belongs to the whole panel (a filter, a badge, a count). Every panel's heading row is at least `min-h-7` tall, with a control or without, so that headings and content line up from panel to panel; this brief may give one row a larger minimum. Content starts `mt-3` under the row.
- **Panels with rows:** a panel that holds a table or a list of rows has no padding of its own and `overflow-hidden`. Its heading row sits in a wrapper with `px-5 pt-5 pb-3` and a 1px bottom border; the padding goes on the wrapper, because a minimum height on a padded row would count the padding. The table or the first row starts directly under the wrapper's border, with no `mt-3`. Each row of a list has `px-5 py-3` and a 1px top border, except the first. A table's cells follow the Tables note instead.
- **Vertical rhythm:** `gap-1` between a label and its value, `gap-2` between a field's label and the field and between controls that belong together, `gap-3` between a heading and what is under it, `gap-5` between panels, between form fields and between groups inside a panel.
- **Labels:** panel headings, table headers, field labels, nav links, and button and badge text are labels: `font-mono text-label uppercase`, a short fragment and never a sentence.
- **Small figures:** a time, a date, an ID, a count with its unit, a person's name, an email address or an address on the web that this brief calls a small figure is `font-mono text-label tabular-nums` in `text-muted-foreground`, without `uppercase`, a name or an email address included (`tabular-nums` leaves letters as they are). Where this brief gives a small figure or a label another color, that color replaces `text-muted-foreground`; do not write both. What someone typed, and the name of a person or a place, is never uppercased. Units stay as they are written (`kg`, `%`).
- **Numbers:** a figure, an ID, a date or a time that stands on its own (in a table cell, as a value, as a small figure) is `font-mono tabular-nums`. This does not apply to a number inside a sentence or an address, to an ID that opens a sentence as a link, or to a value that is a phrase with a number in it (a service such as "Next day, before 18:00"). In a table, numbers are right-aligned, and so is their column header. A key number, the one figure a panel is about, is `font-heading text-h1 tabular-nums`. A clock time or a date that stands on its own is a `<time>` with a `dateTime`.
- **Dates and times:** a date is written `11 Nov`, a date with a time `11 Nov, 14:05`, and a time alone `14:05`, on the 24-hour clock; add the year (`3 Dec 2026`) only where this brief asks for it. Each one that stands on its own (a small figure, a table cell, a value) is a `<time>`; one inside a sentence is not. A duration (`38 min`) is a small figure but not a `<time>`.
- **Text color:** headings, body text and values are `text-foreground`. Labels, small figures and helper text are `text-muted-foreground`.
- **Titles inside a panel:** where this brief asks for a title in `text-body font-medium`, on an `<h3>` or any other element, add `font-sans`: it is body text in a heavier weight, not a heading in the heading font.
- **Measure:** a paragraph is at most `max-w-md` wide (448px, about 65 characters a line in this font).
- **State:** a state is a status dot and a word, side by side: `inline-flex items-center gap-2`. The dot is `size-2 shrink-0 rounded-full` with `aria-hidden="true"`, in `bg-success` for good, `bg-destructive` for failed or late, `bg-tertiary` for caution and `bg-muted-foreground` for neutral or under way; the word is `text-body text-foreground`. Where a state stands on a line of its own under something, a `<div>` around it carries the margin, not the `inline-flex` state. Never show a state by color alone, and never color the word amber: amber fails contrast as text. Use `text-success` and `text-destructive` only for a short change or error message that also has an icon or a word saying what it is.
- **Links:** a text link is `text-primary underline underline-offset-4 hover:text-foreground` and inherits its size. Links get `transition-colors duration-150`.
- **Buttons:** the shadcn `Button` with `className="px-5 font-mono text-label uppercase"`. It keeps its stock height and corners. The default variant (teal) is for the main action of the page or of a panel, and a screen shows at most one enabled default button at a time unless this brief says otherwise. Other actions are `variant="secondary"` (blue-black) or, for quiet ones, `variant="outline"`. In `components/ui/button.tsx`, replace the `secondary` variant's stock hover class, `hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]`, with `hover:bg-secondary/80`, once, and add no hover class beside it or in a `className`: the stock hover is a 5% mix of the resting color, too close to see. This gives every `secondary` button, link styled as one and chosen button of a button group the new hover. A button that sends a form needs `type="submit"`: this `Button` is `type="button"` unless told otherwise. A button that its own click disables while focus stays on it (it saves, or marks something as done, or is Previous or Next at the end of a list) gets `focusableWhenDisabled`, so that keyboard focus stays on it. A button whose click moves focus somewhere else, or removes the button, does not need it; this brief names the buttons that get it. Base UI then sets `aria-disabled` and `data-disabled` in place of the `disabled` attribute, so the stock `disabled:` classes no longer apply: 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`, once. A call to action that goes to another page is an `<a>` with the same classes: write `cn(buttonVariants({ variant }), "…")`, because `buttonVariants` alone does not merge classes. A button that holds only an icon is `size="icon"` with an `aria-label`.
- **Button groups:** a set of buttons where one is chosen (a period, a status filter) is a `role="group"` with `flex flex-wrap gap-1`. Its name is an `aria-label`; only where this brief asks for a visible label above the group is it `aria-labelledby` pointing at that label. The chosen button has `aria-pressed="true"` and `variant="secondary"`; the rest have `aria-pressed="false"` and `variant="outline"`.
- **Badges:** the shadcn `Badge` with `className="h-auto rounded-sm px-2 py-1 font-mono text-label uppercase"`, in the `outline` variant. A caution badge keeps the `outline` variant and adds `border-transparent bg-tertiary text-tertiary-foreground`, which replace the variant's border and text colors through `cn`, and is used only where something needs attention. One or two words.
- **Inputs:** the shadcn `Input` and `Textarea` with `className="text-body md:text-body"`. They keep the fill they ship with, in dark mode too. Every field has a visible `<label>` above it, styled as a label in `text-muted-foreground` and tied to it with `htmlFor`; an `aria-label` alone is enough only where this brief says so. Helper text under a field is one short sentence with `mt-2`, set in the small figure's look (`font-mono text-label text-muted-foreground`, not uppercased) though it is a sentence; the field names it in its `aria-describedby`.
- **Selects:** a choice from a short list is a native `<select>` with the classes `h-8 w-full rounded-lg border border-input bg-card px-2 text-body`. Set a width on an element around it, not on the `<select>`. It gets the same label and focus outline as any field.
- **Errors:** an error is a line with `mt-2`: `flex items-start gap-2 font-mono text-label text-destructive`, not uppercased, holding the `CircleAlert` icon and a sentence that says what to do. It sits under its field; where a field and a button share a row, it sits under the row. 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` or `change`); it does not go away on its own otherwise, and comes back at the next send if the value is still wrong.
- **Confirmations:** a message that appears after an action 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. It stands where the message will show and is never hidden (no `hidden` attribute, no `empty:hidden`). Where a message replaces a form, the two share one wrapper that carries the top margin. Where the message replaces the control that had focus, give it `tabIndex={-1}` and move focus to it.
- **Tables:** the shadcn `Table` with `text-body`. Header cells are labels in `text-muted-foreground`; they get `px-5` and keep their stock `h-10` and `whitespace-nowrap`, so a header is one short line. A unit in a header keeps its case: put it in a `<span>` with `normal-case`. Body and footer cells get `px-5 py-2`; the stock `[&:has([role=checkbox])]:pr-0` on a cell stays. Rows are divided by 1px borders and keep their stock hover fill, also on a table whose rows do nothing: no zebra stripes, and no extra padding to fill space. A table's name is its panel's heading: the `label` prop below repeats that heading's words, and there is no `<caption>`, which would scroll away with the table. Stock cells do not wrap, so a table with many columns is wider than a phone and often wider than a laptop: it scrolls sideways in its own box, which is reachable with the keyboard: `tabIndex={0}`, `role="region"` and an `aria-label`. Edit `components/ui/table.tsx`: put these on its scroll box, with the focus outline drawn inside it (`focus-visible:-outline-offset-2`, since a table here always fills a panel that clips), and add a required `label` prop for the name. A table that only screen readers get is a plain `<table>`, not this component.
- **Icons:** from the project's icon set, with `size-4` and `strokeWidth={1.5}`. An icon that carries meaning has a word beside it; an icon beside a word has `aria-hidden="true"`.
- **Corners:** `rounded-sm` for badges, checkboxes and markers, `rounded-lg` for buttons, inputs and selects (it is what they ship with), `rounded-xl` for panels, `rounded-full` only for status dots.
- **Surfaces:** flat. No shadows, no blur and no gradients in the code you write. A stock component that floats over the page keeps the one short shadow it ships with.
- **Charts:** drawn with plain elements. Bars and lines are `bg-secondary`; at most one, the current value, is `bg-primary`, and it is also named in words. Grid lines are 1px `border-border`. Axis values are small figures. A bar's height in percent counts against a box of fixed height, not a minimum one: this brief says which box that is. Every chart has a text alternative.
- **Menus on small screens:** below `md` the nav links go behind a "Menu" button (`variant="outline"`) with `aria-expanded` and `aria-controls`, which sits at the right end of the row; anything else at the right end of the bar is hidden below `md` unless this brief says it stays. The button shows the links as a list inside the `<header>`, under its row: a second `<nav>` with the same `aria-label`, shown only below `md`, which carries the `id` that `aria-controls` names and has the `hidden` attribute while closed. It has a 1px top border across the full width, and inside the container, with the container's side padding, `py-2`; each link is a block with `py-2`. Do not use a `Sheet` or a `Dialog`. Whether the menu is open is page state, and a page that has no other state still has this one.
- **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 record, if the user gave none: shipment `NG-48213` in "Northgate", the operations console of a regional parcel carrier. It is out for delivery and running late because of a road closure. Invent the customer, the addresses, the items and the events; keep them believable.

Build these parts in order.

### 1. Top bar

The product's name, four nav links (Overview, Shipments, Depots, Reports) with Shipments as the current page, and at the right end the signed-in person's name as a small figure, hidden below `md`.

### 2. Page header

Above the heading, a breadcrumb: a `<nav>` with the `aria-label` "Breadcrumb" holding, with `gap-2`, the link `Shipments` to `/shipments`, styled as a nav link, and the ID as a small figure in `text-foreground` (in place of `text-muted-foreground`), with a `/` between them that has `aria-hidden="true"`. The ID is the current page (`aria-current="page"`, not a link). Here, instead of the Top bar note's value, the nav link Shipments has `aria-current="true"`, so that only one element on the page says `page`. The page header row follows with `mt-2`; the breadcrumb and the row are one grid item.

The `<h1>` is "Shipment NG-48213", with the ID in `font-mono`. Beside it with `gap-3`, its state, "Out for delivery" with a neutral dot, and a caution badge, "Delayed". The actions are an `outline` button "Print label", a `secondary` button "Reroute" and the default button "Mark as delivered".

### 3. Main column

Eight columns from `lg` up, full width below. The column is the grid item: three panels stacked in it with `gap-5`, the last one growing to fill the column (`flex-1`).

**Progress.** Heading: `Progress`. An ordered list of five steps: Booked, Collected, At depot, Out for delivery, Delivered. The list is `grid gap-3 md:grid-cols-5`: from `md` up the steps are a row of five equal columns; below, they are stacked. Each step is `flex gap-3 md:flex-col md:gap-2`: a marker and, beside it below `md` and under it from `md`, the step's name in `text-body font-medium` and under the name a small figure that does not wrap: for a done step the time it was reached, for the current step the time it began (`11 Nov, 08:40`); a step to come has instead the uppercase label `Expected` with the expected time under it; the name, the label and the time are each a block (`<p>`, or a `<time>` with `block`). Every time here has the form `11 Nov, 08:40`, without the year, which fits a step's column at every width from `md` (about 112px at `lg`). The marker is `size-5 rounded-sm border`: a done step's is `border-secondary bg-secondary text-secondary-foreground` with a check icon; the current step's is `border-primary bg-primary` with nothing in it; a step to come has only the border. Each step also says its state for screen readers in an `sr-only` span ("Done", "Current", "To come"), and the current step has `aria-current="step"`.

**Timeline.** A panel with rows. Heading: `Timeline`. Six events, newest first. Each row: the date and time as a small figure, and beside it one sentence in `text-body`, not capped in width, with, under it with `mt-1`, a `<p>` with who or what did it as a small figure. From `md` up the time sits in a column of its own, `w-32`, with `gap-5` to the sentence; below, it sits above the sentence with `mt-1` between them. The newest event is the delay: above its sentence it has a caution state, "Delayed", in a `<div>` with `mb-1` (the margin goes on the wrapper, not on the `inline-flex` state).

**Items.** A panel with rows. Heading: `Items`, with the count at the right end of the heading row as a small figure, `4 items`: it counts the table's rows, not the units (their sum is in the total row). A table with four items and the columns SKU (mono), Description, Quantity (right-aligned) and Weight in kg (right-aligned). A `<tfoot>` row gives the totals, with "Total" in the first cell and `font-medium` on the row. The table is named "Items".

### 4. Side column

Four columns from `lg` up, full width below. The column is the grid item: three panels stacked in it with `gap-5`, the last one growing to fill the column (`flex-1`).

**Details.** Heading: `Details`. A `<dl>` with six rows: Customer, Service, Depot, Weight, Booked and Due. Each row has `py-2` and a 1px top border except the first, with the `<dt>` as a label at the left and, `gap-3` from it, the `<dd>` in `text-body` at the right, right-aligned. Figures and times are mono; a value that is a phrase, such as the service, is not.

**Addresses.** Heading: `Addresses`. Two blocks divided by a 1px border, the second with `mt-5 pt-5`: a label ("From" or "To", an `<h3>` styled as a label) and with `mt-1` an `<address>` in `text-body`, not italic (`not-italic`), with a line for the name, the street, and the postcode and town.

**Notes.** Heading: `Notes`. A form: a labelled `Textarea` "Add a note" (`min-h-24`, no `rows`: it grows with its text, as the stock one does; add it with `npx shadcn@latest add textarea`) and, with `mt-3`, a `secondary` button "Add note". Under the form with `mt-5`, the notes as a list, newest first, two to begin with. Each note has a 1px top border and `py-3`, the last one no bottom padding (`last:pb-0`): the text in `text-body`, and with `mt-1` a `<p>` as a small figure, not a label and not uppercased, with who wrote it and when (`Priya Nair · 11 Nov, 09:12`), where the date and time are a `<time>` inside the figure.

### Behavior

- "Mark as delivered" sets the state to "Delivered" with a success dot, removes the "Delayed" badge, marks all five progress steps as done with the current time on the last, adds a "Delivered" event with a success state to the top of the timeline, and disables itself. The delay event keeps its state. It stays in the page header, disabled, with `focusableWhenDisabled` so that focus stays on it; it is the one button on this page that needs the prop. Announce the change in a `role="status"` element with `sr-only`, empty from the start, placed at the end of the page header's grid item; after the click it reads "Shipment NG-48213 marked as delivered."
- "Add note" adds the note to the top of the list with the signed-in person's name and the current time, and empties the field. With an empty field, or one that holds only spaces, it shows an error line instead: "Write a note first." Focus moves to the field, which reads the line through its `aria-describedby`; the line is not a live region. The line goes away at the next keystroke.
- "Print label", "Reroute" and the links need no 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`, `3`, `5`, `8`, `14`) plus 0, 1px and 2px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-6` 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, 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 12px 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 keep the page calm: most of the screen is grey, white and blue-black.
- Do use color only for state and for the main action.
- Do align numbers and use tabular figures.
- Don't use gradients, glows or glass effects.
- Don't use amber, green or red for decoration.
- 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 progress list says in words which steps are done, which one is current and which are to come, and marks the current one with `aria-current="step"`.
- [ ] The properties are a description list (`<dl>`), and both addresses are `<address>` elements.
- [ ] The "Mark as delivered" button changes the state, the progress and the timeline together, and then cannot be used again.
- [ ] The items table has a total row in a `<tfoot>`, with its numbers right-aligned in mono figures.
- [ ] Adding a note puts it at the top of the list and empties the field; an empty note cannot be added.
- [ ] 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.
