# Build an article page with the Strict One theme

You are building one page in an existing project: a long-form article or blog post page built for reading, with a table of contents. Follow this brief closely. `DESIGN.md` decides the tokens: colors, type, spacing steps and corners. This brief decides the layout and which token goes where. If this brief names a value that is not in `DESIGN.md`, `DESIGN.md` wins.

## Before you start

1. **Check the theme is installed.** The project should have `DESIGN.md` in its root and the Strict One variables in its global CSS. If not, run `npx shadcn@latest add https://strictthemes.com/r/strict-one.json` and save https://strictthemes.com/themes/strict-one/DESIGN.md as `DESIGN.md` in the project root. In a project without shadcn, use https://strictthemes.com/themes/strict-one/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.
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. Unless `lib/utils.ts` already does this, 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:** Archivo for headings, IBM Plex Sans for body text, IBM Plex Mono for labels and code.
- **Type scale** (size/weight; each class also sets line height and tracking): `text-display` 56px/700, `text-h1` 36px/700, `text-h2` 24px/600, `text-body` 16px/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`), 16px (`4`), 32px (`8`), 64px (`16`).
- **Corners:** none 0, sm 2px, md 4px.
- **Colors (light mode):** primary #1F3BFF, on-primary #FFFFFF, secondary #111111, on-secondary #FFFFFF, tertiary #FF4F00, on-tertiary #111111, neutral #6B6B6B, background #FFFFFF, surface #F5F5F4, foreground #111111, border #E2E2E0, error #C62828.

### How Strict One pages are laid out

- **Container:** every section's content sits in a `max-w-[1200px]` container, centered, with `px-4` on small screens and `px-8` from `md` up. This width is the one arbitrary value allowed.
- **Grid:** inside the container, a 12-column grid with `gap-x-8` from `md` up, and a single column below `md`. The grid only sets columns: every vertical distance comes from the rhythm rule, written as `gap-y-*` or `mt-*`, also when the grid is stacked. Align content to the grid's left edge. Do not center text blocks.
- **Sections:** a section ends with `pb-16`. A section that opens with a rule (a section header, or its own top border) has nothing above the rule: the rule is its first line, and the space before it is the previous section's `pb-16`. A section with no rule, such as a hero, also has `pt-16`. A one-line row between sections (a proof row, a single link) has `pb-8` and no top padding. No background bands, no boxes around sections.
- **Vertical rhythm:** `gap-2` between a label and the thing it labels, `gap-4` between a heading and its paragraph and between paragraphs, `gap-8` between blocks, and `mt-8` between a section header and the section's content. In a list of links or features, `gap-2` between items and `gap-4` under the list's label heading. `gap-4` between a button and a link beside it. A header row has `py-4`, with `gap-8` between its items from `md` up and `gap-4` below.
- **Section header:** a section opens with a 1px `border-foreground` top rule and `pt-4`. Under it, columns 1–3 hold a label in the form `01 / FEATURES` in `text-muted-foreground`, and columns 4–12 hold the section heading, an `<h2>` in `text-h1`. Where this brief gives only the label, write the heading yourself. Below `md` the label sits above the heading with `gap-2`.
- **Borders:** every line is 1px and `border-border`, unless this brief says `border-foreground`. The header's bottom line and the footer's top line run the full width of the window; every other line stays inside the container.
- **Labels:** eyebrows, table headers, captions, bylines, nav links, button and badge text, and metadata are labels: `font-mono text-label uppercase`. Write a label as a short fragment, not a sentence; text that needs a full sentence is body text. Email addresses and values in a table or a figure are never uppercased. A "label heading" is a real heading element styled as a label: an `<h2>` in a footer, otherwise `<h2>` or `<h3>`, whichever keeps the heading order.
- **Text color:** body text, headings, label headings and nav links are `text-foreground`. Other labels, captions and metadata are `text-muted-foreground`. A button's text color comes from its variant. Paragraphs are never muted: hierarchy comes from size and weight, not color.
- **Measure:** a paragraph of body text is at most `max-w-lg` wide (512px, 65 to 70 characters a line in this font), however many columns it sits in.
- **Links:** a text link is `text-primary underline underline-offset-4 hover:text-foreground` and inherits its size. A link in a nav or in a footer's link list is `text-foreground` and gets an underline on hover.
- **Buttons:** use the shadcn `Button` with `className="rounded-sm px-4 font-mono text-label uppercase"`, so labels are uppercase mono as `DESIGN.md` asks. 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 and the stock size and corners would win. `Button` is for actions, so on a marketing page most "buttons" in this brief are such links. One default button per screenful. Other actions, a "Menu" button included, use `variant="outline"` or a text link. A group of buttons where one is selected (a filter, a period switch) is a `role="group"` with an `aria-label` and `gap-1`; the selected one has `aria-pressed="true"` and `variant="secondary"`, the rest `variant="outline"`.
- **Badges:** the shadcn `Badge` with `className="rounded-none font-mono text-label uppercase"`, in the `secondary` or `outline` variant. At most one badge in a view is the signal badge `DESIGN.md` describes: the `secondary` variant with `bg-tertiary text-tertiary-foreground` added.
- **Inputs:** the shadcn `Input` with `className="rounded-sm text-body md:text-body"`, and an `aria-label` when it has no visible label.
- **Icons:** from the project's icon set, with `size-4` and `strokeWidth={1.5}`.
- **Corners:** controls use `rounded-sm` (2px), panels and code blocks `rounded-md` (4px), badges and figures inside panels `rounded-none`.
- **Figures:** a figure's caption is a label below its panel, outside it, with `mt-2`.
- **Panels:** a panel is a plain element with a 1px border and `rounded-md`. On a content page, panels that hold a figure or code are `bg-card`; on an app screen every panel is `bg-background`. Do not use the shadcn `Card` for a panel: it ships larger corners and a ring.
- **Surfaces:** flat. No shadows, no blur, no gradients. Where a stock component ships a shadow, a blur or a dimmed overlay (`Sheet`, `DropdownMenu`, `Tooltip`), remove it: with `className` where that reaches, otherwise by editing the file in `components/ui`, which is yours to change. Use a 1px border instead of a shadow and `bg-background/80` for an overlay.
- **Tables:** a table's body text is `text-body`; set it on the `Table` or the `<table>`. Header cells are labels in `text-muted-foreground`. Cells have `px-4 py-2`, in a plain `<table>` and in the stock `Table` alike (its `TableHead` and `TableCell` ship `px-2` and `whitespace-nowrap`). Where the table sits in something that already has padding (a section, a padded panel), add `first:pl-0 last:pr-0`; where it touches a panel's border, do not. Cells that hold a sentence get `whitespace-normal`. A table that can be wider than its box scrolls in a box that is reachable with the keyboard: `tabIndex={0}`, `role="region"` and an `aria-label`. For the stock `Table`, edit `components/ui/table.tsx` and add a prop for the label. A table that always fits needs none of this; one that should scroll on a phone rather than squeeze gets `min-w-lg`.
- **Primary color:** only for the one main action and for links. Never for large fills such as chart bars, bands or panels; those are `bg-foreground`, `bg-card` or a border.
- **Menus on small screens:** a "Menu" button with `aria-expanded` and `aria-controls` that shows the links as a list inside the `<header>`, under its row, with a 1px top border; each link is a block with `py-2`. If the row still does not fit at 360px, move its call to action into the menu too. Use the shadcn `Sheet` only for a full sidebar; such a sheet is `bg-sidebar`, 256px wide (`data-[side=left]:w-64`, since a plain `w-64` loses to the stock width) and has a visually hidden `SheetTitle`. It is rendered outside the page's root, so it also needs `text-body`, plus `overflow-y-auto` and `pt-16` to clear its close button. Its trigger takes `render={<Button … />}` in a Base UI project and `asChild` in a Radix one. A page never has two "Menu" buttons.
- **Motion:** in the code you write, the only transition is color: links and buttons get `transition-colors duration-150`. 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 article, if the user gave none: "Why we removed every gradient from our product", a 600 to 800 word piece by a product designer about replacing decoration with hierarchy. Write the full text yourself, in plain and specific language, with four `<h2>` sections.

Build these parts in order. An `<article>` wraps the article header, the body layout and the article footer.

### 1. Header

A single row with a 1px bottom border: the publication name in `font-heading text-h2`, three label links (Articles, Topics, About) and "Subscribe", an `<a>` styled as an `outline` button. Below `md`, the links go behind a "Menu" button and "Subscribe" stays in the row.

### 2. Article header

- A label line: the topic, the date and the reading time, e.g. `DESIGN · 12 MARCH 2026 · 4 MIN READ`.
- Columns 1–10: the `<h1>` in `text-display` on `md` and up, `text-h1` below.
- Columns 1–8: a standfirst, one or two sentences in `font-sans text-h2 font-normal`.
- A byline label: the author's name and role. No avatar.
- A 1px `border-foreground` rule closes the header.

The article header has `pt-16`, `gap-4` between its parts and `pb-8` above the closing rule. The body layout under the rule has `pt-8`.

### 3. Body layout

From `lg` up, a 12-column grid: columns 1–3 hold the table of contents, columns 4–11 hold the article body. Below `lg`, the table of contents sits above the body as a plain list.

**Table of contents:** a label heading "ON THIS PAGE", then with `gap-4` a list with `gap-2` holding one link per `<h2>` of the article body, as labels. From `lg` up it stays in view while the body scrolls (`lg:sticky lg:top-8 lg:self-start`; without `self-start` a grid item does not stick). Below `lg` it is the same list with the same marking, not sticky. Each link has a 2px left line and `pl-4`. The link for the section in view is `text-foreground` with a `border-foreground` line and `aria-current="location"`; the rest are `text-muted-foreground` with a transparent line. Before the first heading is reached, the first link is the marked one.

**Body:** everything in the body column, the article footer included, is capped at `max-w-lg` (512px, 65 to 70 characters a line in this font). Use `text-body` for paragraphs with `space-y-4` between them. The pull quote, figure, code block, list and table each have `my-8`. The body must include, each styled once and reused:

- `<h2>` headings in `text-h2`, with `mt-16` above them.
- A pull quote: one sentence in `font-heading text-h1`, between 1px top and bottom borders, with `py-8`.
- A figure: a `bg-card` panel with `p-8`, built from elements (for example two small screen sketches side by side labelled "Before" and "After", drawn with `bg-foreground`, `bg-background` and borders, never with the primary color or a gradient), and a `<figcaption>` label.
- A code block: a `bg-card` panel with `p-4`, in `font-mono text-body`, that scrolls sideways inside itself. The code in it is text: it may mention any color or gradient.
- An ordered list of three or four steps: `list-decimal pl-4`, with `space-y-2`.
- A small `Table` with a label-style header row and 1px row borders. On a phone it is wider than the column and scrolls in its box.
- Inline links in `text-primary`, underlined.

### 4. Article footer

In the body column, with `mt-16`: a 1px top border and `pt-8`, then the topics as `outline` badges and, with `mt-4`, one or two sentences about the author.

### 5. Read next

Section header: `01 / READ NEXT`, with an `<h2>` such as "More on design systems". Three article links in a 12-column grid, four columns each. Each has a 1px top border, `pt-4`, a label (topic and reading time) and an `<h3>` in `text-h2` whose link is `text-foreground` with an underline on hover. No images, no cards.

### 6. Footer

A footer with `py-8` and a 1px top border: the publication name in `font-heading text-h2`, a one-line description and a label line with the copyright.

### Behavior

- Mark the current section in the table of contents. Work it out from the headings' positions on scroll: the section in view is the last `<h2>` whose top is above the top third of the window, the first one before any is, and the last one when the page is scrolled to its end. An `IntersectionObserver` on the headings alone misses jumps.
- Jumping to a heading must not hide it under any sticky element: give headings `scroll-mt-8`.

## 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`, `8`, `16`) plus 0 and 1px. No arbitrary values such as `p-[18px]`, and no other steps such as `p-3` or `gap-6`. `auto` margins are fine. Widths and heights such as `w-64` or `size-4` are sizes, not spacing. This applies to the code you write: a stock component keeps the spacing inside it, unless this brief changes it.
- **Type:** every piece of text you write gets its size from one of `text-display`, `text-h1`, `text-h2`, `text-body`, `text-label`, set on the element or on a parent it inherits from. Put `text-body` on the page's root element. 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 for avatars and status dots.
- **Contrast:** all text meets WCAG AA against its background.
- **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. A stock component that ships its own focus ring (`Button`, `buttonVariants`, `Input`, `AccordionTrigger`, the close button of a `Sheet`) also gets `focus-visible:ring-0`, by editing its file in `components/ui` where `className` does not reach. A box that scrolls sideways (a code block, a wide table) gets `tabIndex={0}`, `role="region"`, an `aria-label` and the same outline.
- **Not allowed:** purple or multi-color gradients; emoji used as icons or bullets.
- **Avoid:** 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.

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

- Do keep one accent color per screen.
- Do let whitespace separate content.
- Don't use gradients, glows, glassmorphism or drop shadows.
- Don't use emoji as icons or bullets.
- 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.

- [ ] Body text lines are between 60 and 75 characters long on a desktop screen.
- [ ] The table of contents links jump to their headings, and the heading in view is marked in it.
- [ ] The article is inside an `<article>` element, with one `<h1>` and `<h2>` section headings.
- [ ] Code blocks and the table scroll sideways inside their own box on small screens instead of widening the page.
- [ ] No color is written as a hex, rgb or Tailwind palette value in the page's code.
- [ ] Every padding, margin and gap 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.
