# Build a dashboard with the Strict One theme

You are building one page in an existing project: the overview screen of a web app, with navigation, key numbers, a chart and a table. 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 app, if the user gave none: **Plainsheet**, a shared spreadsheet tool. The dashboard is the workspace overview: how many sheets are active, who is editing, and what changed this week. Invent consistent sample data: about eight table rows and seven days of chart values. Keep every key number to six characters or fewer and every chart value to four digits, so they fit a phone.

This page is an app screen, not a marketing page. It uses the full width of the window instead of the `max-w-[1200px]` container, and it has no section headers of the `01 / NAME` kind. Every panel's label heading is an `<h2>`.

### 1. Shell

From `lg` up, two columns: a sidebar 256px wide (`w-64`) and the main area. Below `lg`, the sidebar is hidden behind a "Menu" button in the top bar, which opens it in a `Sheet` from the left.

**Sidebar:** `bg-sidebar` with a 1px right border and `p-4`. Each link is a block with `px-2 py-2` and `rounded-sm`, and the product name, group names and footer lines share its `px-2`. It is `sticky top-0 h-svh`, with `gap-8` between its parts and `gap-1` between links. Top: the product name, a `<p>` in `font-heading text-h2`. Then a `<nav>` with two groups of label links, each under a group name that is a label in a `<p>`, not a heading (for example Workspace: Overview, Sheets, Members, Activity; Settings: Billing, API keys). The current link is `bg-sidebar-accent text-sidebar-accent-foreground` with `aria-current="page"`. Bottom: the signed-in person's name in `text-body` and email in `font-mono text-label text-muted-foreground`, on two lines, no avatar image.

**Top bar:** a row with a 1px bottom border, `py-4`, and `px-4` below `md`, `px-8` from `md` up. Left: the "Menu" button (below `lg` only) and the `<h1>` "Overview" in `text-h2`. Right: the page's one default button, "New sheet".

### 2. Key numbers

First a row with the label heading `KEY NUMBERS` on the left and, on the right, a period switch: a button group (7 days, 30 days, 90 days). The row wraps on small screens. Then, with `mt-4`, four cells in one bordered strip: a panel with `overflow-hidden` whose grid has `gap-px` and `bg-border`, with `bg-background` cells, so 1px lines show between them at every column count. No shadows. Two columns below `md`, four from `md` up. Each cell, with `p-4`:

- A label, e.g. `ACTIVE SHEETS`.
- The value in `font-mono text-h1 font-normal tabular-nums`.
- A change line in `font-mono text-label`, not uppercased: an arrow icon from the project's icon set and words that fit the selected period, e.g. "Up 12% from the previous 7 days". Use `text-foreground` for it; do not color it green or red.

### 3. Chart

A panel with `p-8` (`p-4` below `md`): a label heading (`EDITS PER DAY`) with the label `LAST 7 DAYS` on the right, then a bar chart. The chart always shows the last seven days, whatever the period switch says.

Build the chart from plain elements, with no chart library: seven columns in a row with `gap-2`. Each column is, top to bottom: a bar area of fixed height (`h-64`) in which the value label and the `bg-foreground` bar sit at the bottom, the label directly on the bar; then the weekday label. The bar's height is its share of the largest value, as a percentage of 90% of the area so the tallest bar's label still fits, set with an inline `style`; that is fine here. Labels are left-aligned with the bar. Write chart values without a thousands separator. The whole visual chart is `aria-hidden`, and a visually hidden plain `<table>` after it gives the same seven values.

### 4. Recent sheets

A panel with a 1px border.

- Header row with `p-4` and a 1px bottom border: the label heading `RECENT SHEETS` on the left; on the right an `Input` with the placeholder "Filter by name" and a status filter, which is a button group (All, Shared, Private). On small screens they wrap under the heading.
- A `Table`: Name, Owner, Status, Edits, Last edited. Sheet names are plain text, not links. Label-style header cells. Edits and dates in `font-mono tabular-nums`, right-aligned. Status is a `Badge` with `rounded-none`: `secondary` for Shared, `outline` for Private. 1px row borders, no zebra stripes. On small screens the table scrolls sideways inside the panel.
- When the filters leave no rows, show one row reading "No sheets match this filter."

### 5. Activity

Beside the table from `2xl` up (table in columns 1–8, activity in 9–12), below it otherwise. A panel with a header row like the table's (`p-4`, the label heading `ACTIVITY`) and six entries with `p-4`. Each entry is one sentence ("Dana renamed Q3 budget") with a time label under it, divided by 1px borders.

### Behavior

- The period switch, the status filter and the name filter keep their value in component state. The period swaps the key numbers between three sets of sample values; the chart and the table do not change with it.
- "New sheet" and the sidebar links need no behavior.
- Main content has `p-8` (`p-4` below `md`) and `gap-8` between panels.

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

- [ ] Every number that can be compared with another is in `font-mono` with `tabular-nums` and right-aligned in tables.
- [ ] Changes (up, down) are stated in words or with a labelled icon, never by color alone.
- [ ] The chart has a text alternative, and its bars are `bg-foreground`, not the primary color.
- [ ] The status filter and the name filter change which rows are shown, and an empty result shows a message.
- [ ] The period switch and the status filter are button groups with `aria-pressed`, not tabs, and the period switch sits with the key numbers it changes.
- [ ] Below `lg` the sidebar is reachable through a "Menu" button that works 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.
