# Build a pricing page with the Strict One theme

You are building one page in an existing project: a pricing page with three plans, a monthly and yearly switch, a comparison table and a FAQ. 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 product, if the user gave none: **Plainsheet**, "spreadsheets your whole team can read". Sample plans: Starter (free, up to 3 people), Team ($12 per person a month, or $10 billed yearly), Business ($24 per person a month, or $20 billed yearly).

Build these sections in order.

### 1. Header

A single row with a 1px bottom border: product name in `font-heading text-h2` on the left, three label links (Product, Templates, Pricing), a "Sign in" label link and an `outline` button "Start free". Below `md` the row holds only the product name, "Start free" and a "Menu" button; the links and "Sign in" move into the menu.

### 2. Intro

- An eyebrow label: `PRICING`.
- Columns 1–8: the `<h1>` in `text-display` on `md` and up, `text-h1` below. Sample: "One price per person. No tiers hidden in a sales call."
- Columns 1–6: one paragraph in `text-body`.

### 3. Plans

This section has no top padding: it follows the intro's `pb-16`. It opens with a visually hidden `<h2>` "Plans".

First, a billing switch: a button group with two buttons, "Monthly" and "Yearly", and a label next to it, with `gap-4`, saying what yearly saves, e.g. `2 MONTHS FREE`. Do not use `Tabs`: there are no tab panels here.

Then, with `mt-8`, three plans side by side, stacked below `md`. They are columns divided by 1px borders, not floating cards: a three-column grid with no gap, one outer 1px border with `rounded-md` around the row, and a 1px border between plans (vertical from `md` up, horizontal below).

Each plan, top to bottom, with `p-8`:

1. The plan name as a label heading, an `<h3>`.
2. The price in `font-heading text-display`, with `/ person / month` as a label beside it. The free plan shows "$0" with the same label.
3. A billing note as a label in `text-muted-foreground`, e.g. `BILLED YEARLY` or `BILLED MONTHLY`. The free plan says `FREE FOREVER`.
4. One sentence saying who the plan is for.
5. The call to action, full width: an `<a>` styled as a button. The recommended plan uses the default variant; the others use `outline`. The three sit on one line across the plans.
6. A 1px top border, then a list of five or six features. Mark each with a small check icon from the project's icon set, in `text-foreground`.

Spacing inside a plan is margins, not gap: `mt-2` before the price and before the billing note, `mt-4` before the sentence, `mt-8` before the call to action, `mt-8` before the list's top border, `pt-8` under that border, and `gap-2` between features. To keep the calls to action level from `md` up, give the row six rows and make each plan `row-span-6 grid grid-rows-subgrid gap-0`, so every part shares a row with its neighbours.

The recommended plan (Team) carries a `Badge` reading "Recommended" next to its name. It is the page's one signal badge, in the tertiary color. Do not enlarge the plan, lift it or tint its background.

### 4. Compare plans

Section header: `01 / COMPARE`. Across all 12 columns, a `Table` with the feature name in the first column and one column per plan. Group rows under three group headings (for example Sharing, History, Admin): one `TableBody` per group, opened by a row with one `<th colSpan={4} scope="rowgroup">` styled as a label in `text-foreground`. The stock `TableBody` drops its last row's border: add `[&_tr:last-child]:border-b` so every row keeps it. Feature names are `<th scope="row">` in `font-sans text-body font-normal`. Plan cells hold "Included", "Not included" or a value such as "30 days", in `font-mono text-body`. Rows are divided by 1px borders, with no zebra stripes and no hover color. On small screens the table scrolls sideways inside its own container.

### 5. FAQ

Section header: `02 / QUESTIONS`. Columns 4–12: an `Accordion` with six questions and short, direct answers about billing, trials, cancelling and data. Each item is divided by a 1px border. The stock `Accordion` is off the type scale: set `items-center gap-4 rounded-none py-4 font-sans text-body font-medium` on the trigger and `pb-4 text-body` on the content, editing `components/ui/accordion.tsx` where `className` does not reach. Both of its chevron icons get `strokeWidth={1.5}`.

### 6. Closing line

A section with a 1px top border and `pt-16`: an `<h2>` in `text-h1` across columns 1–8 ("Still deciding? Start on the free plan."), and one text link to contact sales. No second primary button here, since the plan row may still be on screen.

### 7. Footer

A footer with `py-16`. Four columns from `md` up: the product name in `font-heading text-h2` with a one-line description, then three link groups, each with a label heading and four links in `text-body`. Under them, with `mt-8`, a 1px top border, `pt-4` and a copyright label.

### Behavior

- Keep the billing period in component state. Both the prices and the billing notes read from it.
- The yearly prices are the per-month equivalent; say "billed yearly" so nobody is surprised.

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

- [ ] Switching between monthly and yearly changes every paid plan's price and its billing note, and the selected button has `aria-pressed="true"`.
- [ ] Exactly one plan is marked as recommended, and it has the only default (primary) button in the plan row.
- [ ] The comparison table uses real `<th>` cells with `scope`, and shows included features with words or an icon that has a text alternative, never color alone.
- [ ] Each FAQ item opens and closes with the keyboard.
- [ ] 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.
