# Standards

> The must layer. Checkable rules that bind every screen you ship, human-built or agent-built.

The guidance pages tell you how to decide and what usually works. The catalogue is different: every entry in it is a **standard**, and a standard is a requirement. An L0 violation blocks the release. An L1 deviation needs a documented waiver from a named approver. An L2 deviation needs a stated reason. That holds no matter who or what built the screen.

Each standard is one verifiable statement with explicit fail conditions. "Text meets WCAG AA contrast" is a standard. "Interfaces should feel calm" is not; that's guidance, because no machine can check it. Checkability is what makes this work: it lets the bar be enforced automatically, before anyone sits down to review.

The same catalogue binds both audiences:

- **Humans** read it rendered in the [catalogue](/standards/catalog), and cite IDs in reviews ("this fails SLP-3").
- **AI agents** read it as [YAML](/standards/catalog.yaml) and apply it while generating UI.

The site publishes the same catalogue the harness checks against, with product-neutral wording, from a pinned dx-harness commit.

Standards come in three tiers:

- **L0 — non-negotiable.** Accessibility floors, destructive-action safety. No waiver exists; a violation blocks ship.
- **L1 — mandatory.** Binding by default. Deviation requires a documented waiver in code: `dx-waive <ID> reason="<specific reason>"`.
- **L2 — recommended.** Follow unless you have a better answer; deviating needs an inline rationale.

## All standards

| ID | Tier | Check | Category | Statement |
| --- | --- | --- | --- | --- |
| A11Y-1 | L0 | deterministic | Accessibility | Text meets WCAG AA contrast — 4.5:1 body, 3:1 large text and UI components |
| A11Y-2 | L0 | deterministic | Accessibility | Every interactive element is keyboard-reachable with a visible focus state |
| A11Y-3 | L0 | deterministic | Accessibility | Every form field has a programmatically associated, visible label |
| A11Y-4 | L1 | deterministic | Accessibility | Interactive targets are at least 24x24px (44px on mobile) |
| A11Y-5 | L1 | deterministic | Accessibility | prefers-reduced-motion disables all non-essential animation |
| A11Y-6 | L1 | deterministic | Accessibility | Essential non-text content has a text alternative; decorative content is hidden from assistive technology |
| A11Y-7 | L1 | hybrid | Accessibility | Structure is programmatically determinable — semantic headings, lists, tables, and form groups; headings and labels describe their content |
| A11Y-8 | L1 | hybrid | Accessibility | Custom components expose name, role, and value to assistive technology |
| A11Y-9 | L1 | deterministic | Accessibility | Every page or distinct view has a descriptive title, and the main language is marked up |
| A11Y-10 | L1 | deterministic | Accessibility | Repeated blocks of content are bypassable — skip link or landmark navigation to main content |
| A11Y-11 | L1 | hybrid | Accessibility | Async state changes are announced and focus is managed — transient status via live regions without stealing focus; context replacements move focus to the revealed surface, never both for the same message |
| SLP-1 | L1 | hybrid | Anti-slop | No purple/violet gradient palettes, cyan-on-dark theming, or glow accents |
| SLP-2 | L1 | deterministic | Anti-slop | No gradient text |
| SLP-3 | L1 | deterministic | Anti-slop | No thick side-tab accent borders on rounded cards |
| SLP-4 | L1 | deterministic | Anti-slop | No nested cards; flatten with spacing, typography, and dividers |
| SLP-5 | L2 | judgment | Anti-slop | No icon-tile-above-heading feature-card template; no identical card grids as default layout |
| SLP-6 | L2 | hybrid | Anti-slop | Adjacent type-scale steps differ by at least 1.10x; no flat hierarchy |
| SLP-7 | L2 | judgment | Anti-slop | Spacing has rhythm — related items grouped tighter than unrelated ones |
| SLP-8 | L1 | deterministic | Anti-slop | No bounce or elastic easing on interface elements |
| SLP-9 | L2 | hybrid | Anti-slop | Copy carries no AI-writing tells — marketing buzzwords, em-dash chains, filler, or redundant label/helper pairs |
| SLP-10 | L1 | judgment | Anti-slop | Complex multi-section tasks get a page, not a modal |
| SLP-11 | L2 | judgment | Anti-slop | A card is only for an interactive unit (clickable, selectable, draggable); static content is grouped with spacing, type, and dividers |
| COL-1 | L1 | hybrid | Colour | Primary actions and brand moments use the product's own primary brand colour (or its ramp) |
| COL-2 | L1 | deterministic | Colour | Functional colours (success/warning/danger) come from the shared Radix Colors scales, never ad-hoc; small functional-colour text (≤12px chips/labels) uses the scale's step-12 on a tinted background so it clears AA (A11Y-1) |
| CMP-1 | L1 | hybrid | Components & patterns | Where a Base UI-based component exists for the need, it is used; one-offs require a waiver |
| CMP-2 | L0 | hybrid | Components & patterns | Destructive actions show consequences before executing and offer undo or explicit confirmation |
| CMP-3 | L1 | hybrid | Components & patterns | Every async transaction has visible loading, success, and error states |
| CMP-4 | L1 | judgment | Components & patterns | Every empty-state view unambiguously signals "no content exists" — distinct from loading, error, or permissions failure — through a heading, explanatory subtext, and the absence of loading chrome such as skeleton rows or spinners |
| CMP-5 | L2 | hybrid | Components & patterns | Each view has at most one primary (filled) action; secondary and tertiary actions step down to outline, ghost, or link styling |
| CMP-6 | L2 | hybrid | Components & patterns | Data tables use the table pattern — semantic rows and headers, numeric columns right-aligned with tabular figures, text left-aligned, and a header that stays visible while scrolling |
| CMP-7 | L2 | judgment | Components & patterns | Components stay consistent with their design-system defaults and with sibling-page usage — overriding a default's colour/contrast/shape, or breaking a control group's shared resting affordance, is a finding unless justified |
| CMP-8 | L1 | judgment | Components & patterns | A multi-step or data-entry task offers a non-destructive exit at every step, and in-progress work is preserved or explicitly discarded on interruption — never silently lost |
| CMP-9 | L1 | hybrid | Components & patterns | Content authored by one user and rendered to another is sanitised at the render boundary; author-time schema constraints are not sufficient |
| CMP-10 | L1 | hybrid | Components & patterns | An input-validation error message clears as soon as the field is corrected, with no separate action required; each field's error clears independently among multiple simultaneous errors |
| CMP-11 | L2 | judgment | Components & patterns | A component's own nested interactive child (a focus ring, active-state fill, icon) traces that component's own container edge and radius — not a smaller box floating inside it |
| CNT-1 | L1 | hybrid | Content & naming | Error messages state what happened and what to do next; no raw error codes as the primary message |
| CNT-2 | L1 | judgment | Content & naming | Feature and page names use plain language; no invented portmanteaus, no internal codenames in UI |
| CNT-3 | L2 | hybrid | Content & naming | Copy uses second person and active voice, and keeps sentences to 25 words or fewer |
| CNT-4 | L2 | judgment | Content & naming | Content that models a real-world artifact is faithful to it (scope, terminology, structure) or explicitly labelled illustrative |
| CNT-5 | L2 | hybrid | Content & naming | UI action words name the action, not the input device — "choose", "select", "view", never "click", "tap", "swipe", or "press" |
| CNT-6 | L2 | hybrid | Content & naming | Copy carries no low-informational-value words — no empty openers, filler words, or droppable articles and conjunctions, unless removing them hurts clarity |
| CNT-7 | L2 | judgment | Content & naming | Descriptive copy leads with its purpose — what it does for the user and when to reach for it — before the mechanism |
| CNT-8 | L2 | judgment | Content & naming | Copy replaces nominalised verbs and "to be" constructions with plain action verbs |
| CNT-9 | L2 | hybrid | Content & naming | Copy is clear — one idea per sentence, simple present tense, no double negatives, no noun stacks, short words, acronyms and technical terms have a reachable definition |
| CNT-10 | L1 | judgment | Content & naming | One term per thing within a product — the same action, object, or state keeps a single name everywhere it appears in the interface |
| CNT-11 | L2 | judgment | Content & naming | UI terms match the established word users already meet across other products — "Search" not "Find", "Settings" not "Preferences" |
| CNT-12 | L2 | hybrid | Content & naming | Copy uses sentence case — capitalise the first word and proper or branded nouns only |
| CNT-13 | L2 | hybrid | Content & naming | Copy is free of spelling and proofreading errors and uses British English spelling |
| CNT-14 | L2 | judgment | Content & naming | Copy embodies the DX voice (Clear, Thoughtful, Approachable) and its tone fits the surface context |
| IDN-1 | L1 | hybrid | Identity | Product lockups and logos render only from approved assets; no recreations |
| IDN-2 | L1 | hybrid | Identity | Product icons render only from the approved product-icon family; no ad-hoc, redrawn, or regenerated icons |
| IDN-3 | L2 | judgment | Identity | Copy on a product's surface carries that product's calibrated tone register — same voice character, calibrated weight, never a switched voice system |
| IDN-4 | L1 | judgment | Identity | Casework surfaces treat case data as sensitive: restrained celebration, no gamified or playful elements around case data |
| LAY-1 | L2 | hybrid | Layout | Layout uses the product's declared column grid and gutter scale; ad-hoc column counts and off-scale gutters are findings — N/A where no grid is declared |
| LAY-2 | L1 | judgment | Layout | Layout reflows to a single column with no loss at 320 CSS px (WCAG 2.2 SC 1.4.10); reading order and controls hold at 360/768/1280 |
| LAY-3 | L2 | judgment | Layout | A surface maps to a known page template (workspace view, form, flow step, dashboard, settings, empty state, onboarding) rather than an ad-hoc shell |
| LAY-4 | L2 | hybrid | Layout | Body-text columns cap at a comfortable measure — target ~66ch, never above 80ch (WCAG 1.4.8); full-bleed running text is a finding |
| LAY-5 | L2 | judgment | Layout | Density suits the task — neither cramped for data entry nor padded for scanning; extreme outliers in either direction are findings |
| LAY-6 | L2 | judgment | Layout | Shared edges align; optical alignment is used where geometry misleads |
| LAY-7 | L2 | judgment | Layout | The page has one primary focal region and its visual reading order matches the task's priority order |
| MOT-1 | L2 | hybrid | Motion | Interface motion is 100-300ms with standard easing; no decorative motion on critical paths |
| MOT-2 | L2 | hybrid | Motion | Motion values come from the declared motion token set — durations and easings are never hardcoded in component code |
| MOT-3 | L2 | judgment | Motion | Motion may emphasise meaning but never carry it alone — an animated surface communicates the same information with animations off |
| TOK-1 | L1 | deterministic | Tokens & theming | UI code contains no raw colour values — only the colour tokens DESIGN.md defines (else the codebase's theme tokens, else shadcn's semantic tokens) |
| TOK-2 | L1 | deterministic | Tokens & theming | All spacing values come from the spacing scale DESIGN.md defines (else the codebase's, else the shadcn/Tailwind default scale) |
| TOK-3 | L1 | deterministic | Tokens & theming | Corner radii come from the radius scale DESIGN.md defines (else the codebase's, else the shadcn default radius scale) |
| TYP-1 | L1 | deterministic | Typography | Display and body/UI text use the typefaces and weights the product's DESIGN.md declares (else the codebase's font tokens, else Tailwind's default font stacks); no other typefaces |
| TYP-2 | L1 | deterministic | Typography | Body text at least 14px; labels at least 12px; body line-height 1.5-1.6 |
| TYP-3 | L1 | deterministic | Typography | Type sizes come from the type scale DESIGN.md defines (else the codebase's, else Tailwind's default scale); no off-scale sizes |
| TYP-4 | L2 | deterministic | Typography | Text is never set in all-caps |
| TYP-5 | L2 | hybrid | Typography | Numbers that align in columns or update in place use tabular figures (font-variant-numeric: tabular-nums) |
| TYP-6 | L2 | hybrid | Typography | Running body text is held to a comfortable measure — roughly 45-75 characters per line (target 40-60), via a max-width in ch, not full-viewport paragraphs |

## Fails when

- **A11Y-1**
  - body copy under 4.5:1 against its background
  - gray text on coloured backgrounds that fails AA
- **A11Y-2**
  - focus ring removed without replacement
  - click handlers on non-focusable elements
- **A11Y-3**
  - placeholder used as the only label
  - icon-only button without aria-label
- **A11Y-4**
  - visual target smaller with no expanded hit area
- **A11Y-5**
  - no reduced-motion variant provided
- **A11Y-6**
  - informative image with an empty alt
  - decorative icon exposed to assistive technology (no aria-hidden)
- **A11Y-7**
  - styled divs standing in for headings, lists, or tables
  - headings or labels that do not describe their content
- **A11Y-8**
  - custom control with no role or accessible name
  - visual state change with no ARIA state update
- **A11Y-9**
  - every SPA view sharing one static document title
  - missing or wrong html lang attribute
- **A11Y-10**
  - no skip link and no landmarks on a page with repeated chrome
- **A11Y-11**
  - message announced by both a live region and a focus move
  - transient toast stealing focus
- **SLP-1**
  - the default AI aesthetic anywhere in product UI
- **SLP-2**
  - background-clip text gradients on headings or metrics
- **SLP-3**
  - the single most recognisable AI-UI tell
- **SLP-4**
  - cards inside cards inside cards
- **SLP-5**
  - the universal AI feature-card shape repeated in a grid
- **SLP-6**
  - heading/subheading/body at nearly the same size
- **SLP-7**
  - one spacing value used everywhere
- **SLP-8**
  - dialogs that spring in with overshoot
- **SLP-9**
  - streamline/empower/supercharge/world-class
  - label, sublabel, and helper saying the same thing
  - chatbot artifacts ("Great question!", "I hope this helps")
  - "It's not just X, it's Y" / "serves as a" / forced triads doing rhetoric, not enumeration
- **SLP-10**
  - modal with its own scrollbar and column layout
- **SLP-11**
  - static content boxed in card chrome purely for visual separation
  - a card used as a section wrapper where a heading plus spacing would group it
- **COL-1**
  - primary CTA in a colour that is not the product's primary
  - one product's primary used inside another product's surface
  - a light-hue product primary (orange/amber/lime/yellow) paired with white label text that fails A11Y-1 (use the scale's dark contrast step)
- **COL-2**
  - custom green/red/amber values
  - red used decoratively
  - small functional-colour text (≤12px) on a tint uses step-11 (or lower) and falls below 4.5:1
- **CMP-1**
  - custom dropdown/dialog/tooltip where the stack provides one
- **CMP-2**
  - silent destructive action
  - confirmation that does not state what will be lost
- **CMP-3**
  - missing error state
  - loading state that flickers (no min visible time)
- **CMP-4**
  - a skeleton row, shimmer, or loading spinner is visible in the DOM alongside the empty-state heading
  - the heading/subtext pair reads as "still loading" or "you don't have access" rather than "nothing here yet"
  - an empty state ships with list chrome (row dividers, placeholder rows) that could be mistaken for a populated-but-loading list
- **CMP-5**
  - two or more filled/primary buttons competing in one view with no deliberate co-equal reason
  - a destructive action styled as the primary instead of a distinct destructive variant (CMP-2)
  - every button rendered at the same weight, so nothing reads as the main action
- **CMP-6**
  - numeric columns centre- or left-aligned, or in proportional figures, so values do not line up (TYP-5)
  - a long table whose header scrolls out of view, leaving columns unlabelled
  - tabular data rendered as stacked cards or divs instead of a real table (A11Y-7)
  - a table that can be empty or slow has no designed empty or loading state (CMP-3)
- **CMP-7**
  - a design-system component default is overridden in a way that changes colour, contrast, or shape with no recorded reason
  - a component is used differently here than on sibling pages with no reason
  - members of one control group (toggle set, segmented control) do not share a resting affordance — only the selected one reads as interactive
- **CMP-8**
  - a wizard/dialog with no cancel/back at some step
  - navigation away silently drops typed content
  - an interrupted flow resumes from zero with no warning at exit time
  - escape/close discards a draft with no confirm (ties to CMP-2's mechanics)
- **CMP-9**
  - dangerouslySetInnerHTML/v-html renders another user's authored content with no sanitiser in the render path
  - sanitisation is claimed at editor time only ("schema-constrained output") with nothing enforced at render
  - a prototype defers sanitisation with no recorded flag noting the deferral
- **CMP-10**
  - the error message for a corrected field remains visible after the input satisfies the validation rule again
  - correcting one field's input clears another field's error message, or clears all errors at once
  - the error only clears on an unrelated action (submit, blur elsewhere, navigation) rather than on the correction itself
- **CMP-11**
  - a component's own nested interactive child (a focus ring, active-state fill) does not trace that component's own container edge or radius — it reads as floating inside a smaller box, not outlining the container
- **CNT-1**
  - "Something went wrong" with no next step
  - stack traces or codes shown to users
- **CNT-2**
  - names that require explanation
- **CNT-3**
  - passive constructions in instructions
  - third-person references to the user ("Users can…")
- **CNT-4**
  - a category or level detail a practitioner would recognise as wrong (e.g. a category graded at a level where it does not apply)
  - invented specifics presented as real in a user-testing surface
  - placeholder content with no illustrative label
- **CNT-5**
  - "Click here" / "Tap to continue" / "Press the button" as UI copy
  - a device verb in an instruction a screen reader reads aloud on a touch device
  - ambiguous link text ("click here", "read more") that names the device, not the destination
- **CNT-6**
  - a sentence opening with an empty opener that reads fine without it
  - filler words (just, really, very, please) carrying no meaning in UI copy
- **CNT-7**
  - a title, description, or intro that opens with the mechanism (the tool, token, or library) instead of what it does for the user
- **CNT-8**
  - a "to be" verb carries a nominalised action ("is a requirement", "are in maintenance", "was in transmission")
  - an action is buried in a noun when the verb is available ("give a recommendation" → "recommend")
- **CNT-9**
  - a sentence carries two or more ideas
  - past-perfect or passive constructions where simple present fits
  - a double negative when a positive form exists
  - three or more nouns stacked where a verb or adjective would do
  - a long word from the substitution table used when the plain form exists
  - an acronym or unfamiliar technical term with no reachable definition anywhere — no inline expansion, tooltip, help text, or glossary (terms your users already know excepted)
- **CNT-10**
  - the same action named two or more ways anywhere in the product ("Edit" on one screen, "Amend" on another, "Modify" in a dialog)
  - term drift for the same thing between any two surfaces — a button and its confirmation, a nav item and its page title, a filter and its help text
- **CNT-11**
  - an invented or unfamiliar synonym for a standard UI term ("Find" for a search field, "Preferences" for settings)
  - a coined product term where an established web convention already exists
- **CNT-12**
  - Title Case in a heading, label, or button ("Submit Your Report", "View Project Details")
  - a non-proper mid-sentence word capitalised for emphasis
- **CNT-13**
  - a misspelling or typo in user-facing copy ("recieve", "seperate", "teh")
  - US spelling where British is house style ("color", "organize", "center")
  - a doubled word or copy-paste artifact left in shipped text ("the the", "TODO")
- **CNT-14**
  - an error message that is alarmist or dramatic rather than calm and helpful
  - a success message that gushes instead of acknowledging briefly
  - an empty state or onboarding step that is patronising or careless
  - a destructive confirmation written with drama instead of sober, plain consequences
- **IDN-1**
  - rebuilt or distorted logo marks
- **IDN-2**
  - a redrawn, distorted, or regenerated product-icon mark
  - a per-product background colour or gradient applied to the icon container
  - wordmarks, photos, shadows, or gloss inside the icon
- **IDN-3**
  - copy adopts another product's register (a switched voice system, not calibrated weight)
  - a surface hosted inside another product carries its own nuance instead of the host's voice
- **IDN-4**
  - celebratory or confetti-style motion on a case-closure or case-outcome moment
  - streak / badge / points / leaderboard gamification attached to casework
  - exclamatory congratulatory copy on a sensitive case outcome
- **LAY-1**
  - a declared grid exists and the layout uses an ad-hoc column count not drawn from it
  - a declared grid exists and gutters/margins don't match the spacing scale (TOK-2) with no deliberate reason
- **LAY-2**
  - content or controls become unreachable or ambiguous at 320 px reflow
  - reading order reverses or a primary action disappears at the narrowest width
- **LAY-3**
  - a surface invents a bespoke shell structure when an existing template applies
  - information hierarchy does not match the declared page type
- **LAY-4**
  - body-text column has no max-width and spans the full viewport at 1280
  - measure exceeds 80ch
- **LAY-5**
  - a data-entry surface is too padded for efficient tab traversal
  - a reading or scanning surface is too cramped to scan without error
- **LAY-6**
  - shared edges between sections or components do not align, with no deliberate reason
  - text or icon baselines and optical centres are visibly misaligned
- **LAY-7**
  - two or more regions compete for first read with no task reason for the tie
  - the squint-test first-read lands on secondary content
  - the primary action's region is visually subordinate to decoration
- **MOT-1**
  - animations over 300ms on task flows
  - transition-all
- **MOT-2**
  - a raw duration (350ms, 0.4s) or cubic-bezier literal in component code where the token set exists
  - a surface that animates but declares no motion token set
  - the narrative tier (--motion-story) used on interface or task UI
- **MOT-3**
  - a diagram whose meaning (order, causality, state) exists only in the animation
  - a reduced-motion variant that drops information instead of motion
  - motion that implies a state change that is not happening
- **TOK-1**
  - hex/rgb/hsl literals in component code
- **TOK-2**
  - arbitrary pixel values for margin/padding/gap
- **TOK-3**
  - ad-hoc radius values
  - child radius larger than parent (non-concentric nesting)
  - peer containers of the same kind use different corner radii — anchor to the product's Card/base radius
- **TYP-1**
  - any other typeface in product UI, except a product's registered brand-wordmark typeface confined to its wordmark/logo lockup (declared in the product's DESIGN.md)
- **TYP-2**
  - body copy below 14px
  - line-height under 1.5 on multi-line body text
- **TYP-3**
  - font sizes not on the product's type scale
- **TYP-4**
  - all-caps text — labels, headings, or running text
  - text-transform uppercase used for styling (genuine acronyms excepted)
- **TYP-5**
  - a column of figures (scores, counts, amounts) in proportional numerals that fail to align vertically
  - a live counter, timer, or total that reflows horizontally as its digits change
- **TYP-6**
  - a paragraph that spans the full viewport width with no measure cap
  - body copy running well past ~75ch per line

## Machine source

The catalogue is also published as data at [/standards/catalog.yaml](/standards/catalog.yaml). The same content, inline:

```yaml
# DX Design Harness — control catalogue
# A control is one verifiable statement. If you can't check it, it's not a standard.
# Tiers: L0 = non-negotiable (no waiver) · L1 = mandatory (documented waiver) · L2 = recommended (inline rationale)
# Control rationale and pass/fail examples: /standards/catalog/<id> (append .md for Markdown)
meta:
  version: "0.1"
  updated: "2026-07-12"
  waiver_syntax: dx-waive <ID> reason="<specific reason>"
  categories:
    A11Y: Accessibility
    TOK: Tokens & theming
    TYP: Typography
    COL: Colour
    CMP: Components & patterns
    CNT: Content & naming
    MOT: Motion
    IDN: Identity
    SLP: Anti-slop
    LAY: Layout
controls:
  - id: A11Y-1
    source: DX-DS
    title: Text meets WCAG AA contrast — 4.5:1 body, 3:1 large text and UI components
    tier: L0
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Contrast scan (axe or checks/contrast); zero violations
    waiver: none
    fails_when:
      - body copy under 4.5:1 against its background
      - gray text on coloured backgrounds that fails AA
    enforced: partial
    script: checks/contrast.py
  - id: A11Y-2
    source: DX-DS
    title: Every interactive element is keyboard-reachable with a visible focus state
    tier: L0
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: Keyboard traversal covers all interactive elements; each shows a visible
      focus state
    waiver: none
    fails_when:
      - focus ring removed without replacement
      - click handlers on non-focusable elements
    enforced: partial
    script: checks/a11y-static.py
  - id: A11Y-3
    source: DX-DS
    title: Every form field has a programmatically associated, visible label
    tier: L0
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: Every input has an associated visible label element; checks/a11y-static
    waiver: none
    fails_when:
      - placeholder used as the only label
      - icon-only button without aria-label
    enforced: partial
    script: checks/a11y-static.py
  - id: A11Y-4
    source: DX-DS
    title: Interactive targets are at least 24x24px (44px on mobile)
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: Computed hit area of interactive elements >= 24x24 CSS px (>= 44px at
      mobile widths)
    waiver: documented
    fails_when:
      - visual target smaller with no expanded hit area
  - id: A11Y-5
    source: DX-DS
    title: prefers-reduced-motion disables all non-essential animation
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: With prefers-reduced-motion set, non-essential animation does not run
    waiver: documented
    fails_when:
      - no reduced-motion variant provided
  - id: A11Y-6
    source: DX-DS
    title: Essential non-text content has a text alternative; decorative content is
      hidden from assistive technology
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Every img/svg/icon either has a text alternative (alt, aria-label,
      aria-labelledby) or is marked decorative (aria-hidden, empty alt)
    waiver: documented
    fails_when:
      - informative image with an empty alt
      - decorative icon exposed to assistive technology (no aria-hidden)
  - id: A11Y-7
    source: DX-DS
    title: Structure is programmatically determinable — semantic headings, lists,
      tables, and form groups; headings and labels describe their content
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
    verify: Script asserts a table or role=table carries header cells
      (checks/structure-scan.py); the rendered runner covers heading order and
      list semantics; evaluator judges headings/labels are descriptive and link
      text makes sense out of context
    waiver: documented
    fails_when:
      - styled divs standing in for headings, lists, or tables
      - headings or labels that do not describe their content
    enforced: partial
    script: checks/structure-scan.py
  - id: A11Y-8
    source: DX-DS
    title: Custom components expose name, role, and value to assistive technology
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: Script asserts custom controls carry role + accessible name + state
      (e.g. aria-pressed/expanded/checked); evaluator judges the exposed
      semantics match the visual behavior
    waiver: documented
    fails_when:
      - custom control with no role or accessible name
      - visual state change with no ARIA state update
    enforced: partial
    script: checks/a11y-static.py
  - id: A11Y-9
    source: DX-DS
    title: Every page or distinct view has a descriptive title, and the main
      language is marked up
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
    verify: Document title describes the page purpose; html lang attribute present
      and correct; SPA views update the title
    waiver: documented
    fails_when:
      - every SPA view sharing one static document title
      - missing or wrong html lang attribute
  - id: A11Y-10
    source: DX-DS
    title: Repeated blocks of content are bypassable — skip link or landmark
      navigation to main content
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
    verify: A skip-to-main link is the first focusable element, or main/nav
      landmarks allow AT users to bypass repeated chrome; n/a only for pages
      with no repeated chrome
    waiver: documented
    fails_when:
      - no skip link and no landmarks on a page with repeated chrome
  - id: A11Y-11
    source: DX-DS
    title: Async state changes are announced and focus is managed — transient status
      via live regions without stealing focus; context replacements move focus
      to the revealed surface, never both for the same message
    tier: L1
    check: hybrid
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - flow
      - component
    verify: Script asserts each async state change has either a live region or a
      focus target; evaluator judges the choice (transient → live region;
      context change → focus move) and that no message is announced twice
    waiver: documented
    fails_when:
      - message announced by both a live region and a focus move
      - transient toast stealing focus
  - id: TOK-1
    source: DX-DS
    title: UI code contains no raw colour values — only the colour tokens DESIGN.md
      defines (else the codebase's theme tokens, else shadcn's semantic tokens)
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Scan changed files for raw hex/rgb/hsl; checks/token-audit; exit 1 on
      violation
    waiver: documented
    fails_when:
      - hex/rgb/hsl literals in component code
    enforced: script
    script: checks/token-audit.py
  - id: TOK-2
    source: DX-DS
    title: All spacing values come from the spacing scale DESIGN.md defines (else
      the codebase's, else the shadcn/Tailwind default scale)
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "No margin/padding/gap values off the resolved spacing scale;
      checks/token-audit checks only raw CSS px/rem values, against the fixed
      shadcn/Tailwind default set, so compare token values and spacing utilities
      (named and arbitrary) against the product's scale by hand; when the
      product's scale differs from the default, a finding means only that the
      value is off the default set: check it against the product's scale before
      you report it, and check the raw values the script passes as well"
    waiver: documented
    fails_when:
      - arbitrary pixel values for margin/padding/gap
    enforced: script
    script: checks/token-audit.py
  - id: TOK-3
    source: DX-DS
    title: Corner radii come from the radius scale DESIGN.md defines (else the
      codebase's, else the shadcn default radius scale)
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: No off-scale border-radius values; checks/token-audit
    waiver: documented
    fails_when:
      - ad-hoc radius values
      - child radius larger than parent (non-concentric nesting)
      - peer containers of the same kind use different corner radii — anchor to
        the product's Card/base radius
    enforced: partial
    script: checks/token-audit.py
  - id: TYP-1
    source: DX-DS
    title: Display and body/UI text use the typefaces and weights the product's
      DESIGN.md declares (else the codebase's font tokens, else Tailwind's
      default font stacks); no other typefaces
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Font-family scan; only the typefaces and weights DESIGN.md declares
      (else the codebase's font tokens, else Tailwind's defaults), plus each
      product's registered brand-wordmark typeface used solely for its
      wordmark/logo lockup (declared in the product's DESIGN.md);
      checks/type-scan
    waiver: documented
    fails_when:
      - any other typeface in product UI, except a product's registered
        brand-wordmark typeface confined to its wordmark/logo lockup (declared
        in the product's DESIGN.md)
    enforced: partial
    script: checks/type-scan.py
  - id: TYP-2
    source: DX-DS
    title: Body text at least 14px; labels at least 12px; body line-height 1.5-1.6
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Type-scale scan; checks/type-scan
    waiver: documented
    fails_when:
      - body copy below 14px
      - line-height under 1.5 on multi-line body text
    enforced: partial
    script: checks/type-scan.py
  - id: TYP-3
    source: DX-DS
    title: Type sizes come from the type scale DESIGN.md defines (else the
      codebase's, else Tailwind's default scale); no off-scale sizes
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Sizes on the resolved type scale; checks/type-scan tests against
      Tailwind's default set {128,96,72,60,48,36,30,24,20,18,16,14,12}
    waiver: documented
    fails_when:
      - font sizes not on the product's type scale
    enforced: script
    script: checks/type-scan.py
  - id: TYP-4
    source: DX-DS
    title: Text is never set in all-caps
    tier: L2
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Uppercase text-transform or all-caps strings flagged anywhere (labels,
      headings, body); genuine acronyms exempt; checks/type-scan
    waiver: rationale
    fails_when:
      - all-caps text — labels, headings, or running text
      - text-transform uppercase used for styling (genuine acronyms excepted)
    enforced: script
    script: checks/type-scan.py
  - id: TYP-5
    source: DX-DS
    title: "Numbers that align in columns or update in place use tabular figures
      (font-variant-numeric: tabular-nums)"
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Numeric columns/tables and dynamically-updating numbers carry
      font-variant-numeric: tabular-nums (Tailwind `tabular-nums`); evaluator
      judges which figures align or update, and confirms the markup carries the
      class; no script, per this control's accepted gap"
    waiver: rationale
    fails_when:
      - a column of figures (scores, counts, amounts) in proportional numerals
        that fail to align vertically
      - a live counter, timer, or total that reflows horizontally as its digits
        change
  - id: TYP-6
    source: DX-DS
    title: Running body text is held to a comfortable measure — roughly 45-75
      characters per line (target 40-60), via a max-width in ch, not
      full-viewport paragraphs
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Running-prose blocks carry a max-width measure cap (Tailwind
      `max-w-prose` / `max-w-[65ch]`); evaluator judges which blocks are running
      prose versus headings, tables, and data grids; checks/type-scan measure
      rule (built in #161)"
    waiver: rationale
    fails_when:
      - a paragraph that spans the full viewport width with no measure cap
      - body copy running well past ~75ch per line
    enforced: partial
    script: checks/type-scan.py
  - id: COL-1
    source: DX-DS
    title: Primary actions and brand moments use the product's own primary brand
      colour (or its ramp)
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Deterministic half: primary-action and brand-moment colours resolve to
      the active product's primary token or its ramp (checks/token-audit).
      Judgment half: the evaluator confirms the view's single primary action is
      the product primary (dovetails CMP-5) and no other product's primary
      appears for emphasis; see its detail page for the foreground pairing; the
      product's primary is declared in its DESIGN.md"
    waiver: documented
    fails_when:
      - primary CTA in a colour that is not the product's primary
      - one product's primary used inside another product's surface
      - a light-hue product primary (orange/amber/lime/yellow) paired with white
        label text that fails A11Y-1 (use the scale's dark contrast step)
    enforced: partial
    script: checks/token-audit.py
  - id: COL-2
    source: DX-DS
    title: Functional colours (success/warning/danger) come from the shared Radix
      Colors scales, never ad-hoc; small functional-colour text (≤12px
      chips/labels) uses the scale's step-12 on a tinted background so it clears
      AA (A11Y-1)
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Success/warning/danger/info colours resolve to Radix scale tokens, and
      small functional-colour text on a tint resolves to step-12 (step-11 on a
      step-3 tint lands ~4.25:1 and fails AA); contrast computed by
      checks/contrast / verified manually until wired
    waiver: documented
    fails_when:
      - custom green/red/amber values
      - red used decoratively
      - small functional-colour text (≤12px) on a tint uses step-11 (or lower)
        and falls below 4.5:1
    enforced: partial
    script:
      - checks/token-audit.py
      - checks/contrast.py
  - id: CMP-1
    source: DX-DS
    title: Where a Base UI-based component exists for the need, it is used; one-offs
      require a waiver
    tier: L1
    check: hybrid
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Component usage diffed against the product manifest; evaluator judges
      'exists for the need' edge cases; record carries one of the three fixed
      CMP-1 verdict forms (see controls/cmp-1.md)
    waiver: documented
    fails_when:
      - custom dropdown/dialog/tooltip where the stack provides one
    enforced: partial
    script: checks/component-manifest.py
  - id: CMP-2
    source: DX-DS
    title: Destructive actions show consequences before executing and offer undo or
      explicit confirmation
    tier: L0
    check: hybrid
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - flow
      - component
    verify: Script enumerates destructive actions and asserts a consequence +
      undo/confirm surface exists; evaluator judges the consequence copy is
      plain and complete
    waiver: none
    fails_when:
      - silent destructive action
      - confirmation that does not state what will be lost
  - id: CMP-3
    source: DX-DS
    title: Every async transaction has visible loading, success, and error states
    tier: L1
    check: hybrid
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - flow
    verify: Script confirms the three states exist per async action; evaluator
      judges each state communicates clearly
    waiver: documented
    fails_when:
      - missing error state
      - loading state that flickers (no min visible time)
  - id: CMP-4
    source: DX-DS
    title: Every empty-state view unambiguously signals "no content exists" —
      distinct from loading, error, or permissions failure — through a heading,
      explanatory subtext, and the absence of loading chrome such as skeleton
      rows or spinners
    tier: L1
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Evaluator reads the empty-state view: no skeleton-row element and no
      loading spinner render alongside the empty-state heading, and the heading
      + subtext pair answers 'could a first-time user mistake this for a loading
      state or a permissions error?' — pass = no plausible confusion"
    waiver: documented
    fails_when:
      - a skeleton row, shimmer, or loading spinner is visible in the DOM
        alongside the empty-state heading
      - the heading/subtext pair reads as "still loading" or "you don't have
        access" rather than "nothing here yet"
      - an empty state ships with list chrome (row dividers, placeholder rows)
        that could be mistaken for a populated-but-loading list
  - id: CMP-5
    source: DX-DS
    title: Each view has at most one primary (filled) action; secondary and tertiary
      actions step down to outline, ghost, or link styling
    tier: L2
    check: hybrid
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Evaluator judges the view/region boundaries, counts
      filled/primary-variant buttons inside each, and allows any deliberately
      co-equal actions; no script, per this control's accepted gap
    waiver: rationale
    fails_when:
      - two or more filled/primary buttons competing in one view with no
        deliberate co-equal reason
      - a destructive action styled as the primary instead of a distinct
        destructive variant (CMP-2)
      - every button rendered at the same weight, so nothing reads as the main
        action
  - id: CMP-6
    source: DX-DS
    title: Data tables use the table pattern — semantic rows and headers, numeric
      columns right-aligned with tabular figures, text left-aligned, and a
      header that stays visible while scrolling
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - component
      - page
    verify: Script asserts table/th and role=table/columnheader semantics
      (checks/structure-scan.py); evaluator judges numeric alignment and tabular
      figures, header persistence, density, and that empty/loading states are
      designed (CMP-3)
    waiver: rationale
    fails_when:
      - numeric columns centre- or left-aligned, or in proportional figures, so
        values do not line up (TYP-5)
      - a long table whose header scrolls out of view, leaving columns unlabelled
      - tabular data rendered as stacked cards or divs instead of a real table
        (A11Y-7)
      - a table that can be empty or slow has no designed empty or loading state
        (CMP-3)
    enforced: partial
    script: checks/structure-scan.py
  - id: CMP-7
    source: DX-DS
    title: Components stay consistent with their design-system defaults and with
      sibling-page usage — overriding a default's colour/contrast/shape, or
      breaking a control group's shared resting affordance, is a finding unless
      justified
    tier: L2
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Evaluator: the surface's components use their design-system defaults;
      any override of a default that changes colour, contrast, or shape is
      flagged (and its contrast re-checked under A11Y-1), the surface's
      component usage matches sibling pages, and a control group's members share
      one resting affordance"
    waiver: rationale
    fails_when:
      - a design-system component default is overridden in a way that changes
        colour, contrast, or shape with no recorded reason
      - a component is used differently here than on sibling pages with no reason
      - members of one control group (toggle set, segmented control) do not
        share a resting affordance — only the selected one reads as interactive
  - id: CMP-8
    source: DX-DS
    title: A multi-step or data-entry task offers a non-destructive exit at every
      step, and in-progress work is preserved or explicitly discarded on
      interruption — never silently lost
    tier: L1
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - flow
    verify: Evaluator walks the flow map, confirms every step has a reachable
      cancel/back affordance, interrupts the flow, and confirms in-progress work
      survives the interruption or is explicitly, confirmably discarded — never
      silently lost
    waiver: documented
    fails_when:
      - a wizard/dialog with no cancel/back at some step
      - navigation away silently drops typed content
      - an interrupted flow resumes from zero with no warning at exit time
      - escape/close discards a draft with no confirm (ties to CMP-2's mechanics)
  - id: CMP-9
    source: DX-DS
    title: Content authored by one user and rendered to another is sanitised at the
      render boundary; author-time schema constraints are not sufficient
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - component
      - flow
    verify: "Deterministic: checks/cmp-scan.py (planned in #158, not yet built)
      finds dangerouslySetInnerHTML/v-html on any surface rendering another
      user's authored content, and checks whether a sanitiser call sits in the
      render path. Judgment: evaluator reads the render boundary and confirms
      sanitisation holds there, not only at author/editor time; a mock-data
      prototype deferral is acceptable only if explicitly flagged in the
      decision record"
    waiver: documented
    fails_when:
      - dangerouslySetInnerHTML/v-html renders another user's authored content
        with no sanitiser in the render path
      - sanitisation is claimed at editor time only ("schema-constrained
        output") with nothing enforced at render
      - a prototype defers sanitisation with no recorded flag noting the deferral
  - id: CMP-10
    source: DX-DS
    title: An input-validation error message clears as soon as the field is
      corrected, with no separate action required; each field's error clears
      independently among multiple simultaneous errors
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: "Deterministic: for each in-scope field, enter an invalid value, confirm
      the error message renders, then correct it and confirm the error message
      is removed from the DOM (planned interaction script, manual until built).
      Judgment: with multiple simultaneous field errors, correct one field and
      confirm only that field's error clears while the others remain visible."
    waiver: documented
    fails_when:
      - the error message for a corrected field remains visible after the input
        satisfies the validation rule again
      - correcting one field's input clears another field's error message, or
        clears all errors at once
      - the error only clears on an unrelated action (submit, blur elsewhere,
        navigation) rather than on the correction itself
    enforced: manual
    status: proposed
  - id: CMP-11
    source: DX-DS
    title: A component's own nested interactive child (a focus ring, active-state
      fill, icon) traces that component's own container edge and radius — not a
      smaller box floating inside it
    tier: L2
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Evaluator: for each interactive component, does a focus ring,
      active-state fill, or icon nested inside it trace the container's true
      edge and radius (accounting for padding), or does it float inside a
      smaller box than the container it's meant to outline? A self-consistency
      read distinct from A11Y-8 (state-tracking) — a ring can correctly track
      aria-pressed and still sit in the wrong box"
    waiver: rationale
    fails_when:
      - a component's own nested interactive child (a focus ring, active-state
        fill) does not trace that component's own container edge or radius — it
        reads as floating inside a smaller box, not outlining the container
    status: proposed
  - id: CNT-1
    source: DX-DS
    title: Error messages state what happened and what to do next; no raw error
      codes as the primary message
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Script finds error surfaces and flags raw codes; evaluator judges 'what
      happened -> what it means -> what to do next'
    waiver: documented
    fails_when:
      - '"Something went wrong" with no next step'
      - stack traces or codes shown to users
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-2
    source: DX-DS
    title: Feature and page names use plain language; no invented portmanteaus, no
      internal codenames in UI
    tier: L1
    check: judgment
    phase:
      - intent
      - plan
      - verify
    applies_to:
      - content
    verify: "Evaluator: would a user understand the name without explanation?
      Portmanteaus and codenames are findings"
    waiver: documented
    fails_when:
      - names that require explanation
  - id: CNT-3
    source: DX-DS
    title: Copy uses second person and active voice, and keeps sentences to 25 words
      or fewer
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint sentence length; evaluator judges voice and person
    waiver: rationale
    fails_when:
      - passive constructions in instructions
      - third-person references to the user ("Users can…")
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-4
    source: DX-DS
    title: Content that models a real-world artifact is faithful to it (scope,
      terminology, structure) or explicitly labelled illustrative
    tier: L2
    check: judgment
    phase:
      - intent
      - implement
      - verify
    applies_to:
      - content
    verify: Evaluator + named domain reviewer sign-off before user testing, or the
      illustrative label present in-product and in the decision record
    waiver: rationale
    fails_when:
      - a category or level detail a practitioner would recognise as wrong (e.g.
        a category graded at a level where it does not apply)
      - invented specifics presented as real in a user-testing surface
      - placeholder content with no illustrative label
  - id: CNT-5
    source: DX-DS
    title: UI action words name the action, not the input device — "choose",
      "select", "view", never "click", "tap", "swipe", or "press"
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint flags device-bound verbs (click/tap/swipe) in user-facing copy;
      evaluator confirms the word is a UI action instruction and judges the
      harder calls (press, see) and ambiguous link text
    waiver: rationale
    fails_when:
      - '"Click here" / "Tap to continue" / "Press the button" as UI copy'
      - a device verb in an instruction a screen reader reads aloud on a touch
        device
      - ambiguous link text ("click here", "read more") that names the device,
        not the destination
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-6
    source: DX-DS
    title: Copy carries no low-informational-value words — no empty openers, filler
      words, or droppable articles and conjunctions, unless removing them hurts
      clarity
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint flags sentence-initial empty openers (There is, There are, It is,
      This is) and the safe filler subset (just, really, very, please);
      evaluator judges the context-dependent words (such, that, articles and
      conjunctions) and the clarity exception
    waiver: rationale
    fails_when:
      - a sentence opening with an empty opener that reads fine without it
      - filler words (just, really, very, please) carrying no meaning in UI copy
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-7
    source: DX-DS
    title: Descriptive copy leads with its purpose — what it does for the user and
      when to reach for it — before the mechanism
    tier: L2
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: "Evaluator reads the first line of titles, descriptions, section intros,
      empty states, and feature blurbs: purpose or role first, mechanism after"
    waiver: rationale
    fails_when:
      - a title, description, or intro that opens with the mechanism (the tool,
        token, or library) instead of what it does for the user
  - id: CNT-8
    source: DX-DS
    title: Copy replaces nominalised verbs and "to be" constructions with plain
      action verbs
    tier: L2
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Evaluator checks prose for noun endings (-ance, -ment, -sion, -tion,
      -ure, etc.) paired with a 'to be' verb and flags each; quotes the noun
      phrase and gives the plain-verb rewrite
    waiver: rationale
    fails_when:
      - a "to be" verb carries a nominalised action ("is a requirement", "are in
        maintenance", "was in transmission")
      - an action is buried in a noun when the verb is available ("give a
        recommendation" → "recommend")
  - id: CNT-9
    source: DX-DS
    title: Copy is clear — one idea per sentence, simple present tense, no double
      negatives, no noun stacks, short words, acronyms and technical terms have
      a reachable definition
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint copy for long-word substitutions (word-list clause); evaluator
      judges the remaining clauses, including whether each acronym or technical
      term has a reachable definition (inline, tooltip, help text, or glossary —
      checked on the rendered surface, not just the copy string)
    waiver: rationale
    fails_when:
      - a sentence carries two or more ideas
      - past-perfect or passive constructions where simple present fits
      - a double negative when a positive form exists
      - three or more nouns stacked where a verb or adjective would do
      - a long word from the substitution table used when the plain form exists
      - an acronym or unfamiliar technical term with no reachable definition
        anywhere — no inline expansion, tooltip, help text, or glossary (terms
        your users already know excepted)
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-10
    source: DX-DS
    title: One term per thing within a product — the same action, object, or state
      keeps a single name everywhere it appears in the interface
    tier: L1
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Evaluator reviews the product's surfaces for term drift — the same
      action, object, or state named two ways anywhere it appears (nav, buttons,
      labels, filters, dialogs, messages, tooltips, empty states, help text).
      The listed surfaces are examples, not the scope; the whole interface is in
      scope.
    waiver: documented
    fails_when:
      - the same action named two or more ways anywhere in the product ("Edit"
        on one screen, "Amend" on another, "Modify" in a dialog)
      - term drift for the same thing between any two surfaces — a button and
        its confirmation, a nav item and its page title, a filter and its help
        text
  - id: CNT-11
    source: DX-DS
    title: UI terms match the established word users already meet across other
      products — "Search" not "Find", "Settings" not "Preferences"
    tier: L2
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Evaluator checks common UI terms against the convention users see across
      the web and other products; an invented or unfamiliar synonym for a
      standard term is a finding
    waiver: rationale
    fails_when:
      - an invented or unfamiliar synonym for a standard UI term ("Find" for a
        search field, "Preferences" for settings)
      - a coined product term where an established web convention already exists
  - id: CNT-12
    source: DX-DS
    title: Copy uses sentence case — capitalise the first word and proper or branded
      nouns only
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint headings/labels/buttons for Title-Case patterns (planned
      content-lint heuristic); evaluator judges sentence-case compliance and the
      proper-noun / branded-term exemptions
    waiver: rationale
    fails_when:
      - Title Case in a heading, label, or button ("Submit Your Report", "View
        Project Details")
      - a non-proper mid-sentence word capitalised for emphasis
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-13
    source: DX-DS
    title: Copy is free of spelling and proofreading errors and uses British English
      spelling
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint flags a curated US→UK spelling map and a common-misspelling list
      read from cnt-13.md; evaluator proofreads for the typos, homophones, and
      doubled words no word list can catch
    waiver: rationale
    fails_when:
      - a misspelling or typo in user-facing copy ("recieve", "seperate", "teh")
      - US spelling where British is house style ("color", "organize", "center")
      - a doubled word or copy-paste artifact left in shipped text ("the the",
        "TODO")
    enforced: partial
    script: checks/content-lint.py
  - id: CNT-14
    source: DX-DS
    title: Copy embodies the DX voice (Clear, Thoughtful, Approachable) and its tone
      fits the surface context
    tier: L2
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: "Evaluator reads the copy against the voice attributes and the
      tone-by-context table in voice-tone.mdx: does it sound
      Clear/Thoughtful/Approachable (not cold, robotic, patronising, alarmist,
      or sappy), and does the tone match the surface (affirming success, calm
      error, sober destructive, inviting empty state)?"
    waiver: rationale
    fails_when:
      - an error message that is alarmist or dramatic rather than calm and
        helpful
      - a success message that gushes instead of acknowledging briefly
      - an empty state or onboarding step that is patronising or careless
      - a destructive confirmation written with drama instead of sober, plain
        consequences
  - id: MOT-1
    source: DX-DS
    title: Interface motion is 100-300ms with standard easing; no decorative motion
      on critical paths
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Animation durations in 100-300ms with standard easing; none on critical
      paths beyond functional feedback
    waiver: rationale
    fails_when:
      - animations over 300ms on task flows
      - transition-all
  - id: MOT-2
    source: DX-DS
    title: Motion values come from the declared motion token set — durations and
      easings are never hardcoded in component code
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - component
      - page
    verify: "Grep transition/animation values in component code: every duration and
      easing resolves to a motion token (--motion-*/--ease-* or their declared
      code mirror); no raw ms or cubic-bezier literals outside the token
      definitions"
    waiver: rationale
    fails_when:
      - a raw duration (350ms, 0.4s) or cubic-bezier literal in component code
        where the token set exists
      - a surface that animates but declares no motion token set
      - the narrative tier (--motion-story) used on interface or task UI
    enforced: manual
    status: proposed
  - id: MOT-3
    source: DX-DS
    title: Motion may emphasise meaning but never carry it alone — an animated
      surface communicates the same information with animations off
    tier: L2
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "With prefers-reduced-motion set, every diagram and animated surface
      still communicates its full meaning: states, order, gates, and
      relationships remain legible statically"
    waiver: rationale
    fails_when:
      - a diagram whose meaning (order, causality, state) exists only in the
        animation
      - a reduced-motion variant that drops information instead of motion
      - motion that implies a state change that is not happening
    status: proposed
  - id: IDN-1
    source: DX-DS
    title: Product lockups and logos render only from approved assets; no recreations
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
    verify: Logo/lockup files resolve to the approved asset library; no inline redraws
    waiver: documented
    fails_when:
      - rebuilt or distorted logo marks
  - id: IDN-2
    source: DX-DS
    title: Product icons render only from the approved product-icon family; no
      ad-hoc, redrawn, or regenerated icons
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Icon files resolve to the approved product-icon set (the guideline's
      icon family); no inline-drawn or regenerated marks; new marks are
      pre-verified via the Icon Generator before entering the set
    waiver: documented
    fails_when:
      - a redrawn, distorted, or regenerated product-icon mark
      - a per-product background colour or gradient applied to the icon container
      - wordmarks, photos, shadows, or gloss inside the icon
  - id: IDN-3
    source: DX-DS
    title: Copy on a product's surface carries that product's calibrated tone
      register — same voice character, calibrated weight, never a switched voice
      system
    tier: L2
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: "Evaluator reads the surface's copy against the tone register the
      product declares in its DESIGN.md: does it hold the shared voice character
      at the product's calibrated weight, or switch systems?"
    waiver: rationale
    fails_when:
      - copy adopts another product's register (a switched voice system, not
        calibrated weight)
      - a surface hosted inside another product carries its own nuance instead
        of the host's voice
  - id: IDN-4
    source: DX-DS
    title: "Casework surfaces treat case data as sensitive: restrained celebration,
      no gamified or playful elements around case data"
    tier: L1
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - content
      - page
      - component
    verify: On casework surfaces, the evaluator confirms case-data moments are
      treated with restraint — no confetti/celebration animations,
      streak/badge/points gamification, or exclamatory congratulatory copy
      around case outcomes; acknowledgement is calm and privacy-forward
    waiver: documented
    fails_when:
      - celebratory or confetti-style motion on a case-closure or case-outcome
        moment
      - streak / badge / points / leaderboard gamification attached to casework
      - exclamatory congratulatory copy on a sensitive case outcome
  - id: SLP-1
    source: DX-DS
    title: No purple/violet gradient palettes, cyan-on-dark theming, or glow accents
    tier: L1
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Stylesheet scan: no purple/violet gradient palettes, no cyan-on-dark
      theming, no glow box-shadow accents"
    waiver: documented
    fails_when:
      - the default AI aesthetic anywhere in product UI
  - id: SLP-2
    source: DX-DS
    title: No gradient text
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: Scan for background-clip:text (and -webkit- variant) gradient fills on text
    waiver: documented
    fails_when:
      - background-clip text gradients on headings or metrics
  - id: SLP-3
    source: DX-DS
    title: No thick side-tab accent borders on rounded cards
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: Scan for thick (>= 3px) left/side accent borders combined with
      border-radius on card containers
    waiver: documented
    fails_when:
      - the single most recognisable AI-UI tell
  - id: SLP-4
    source: DX-DS
    title: No nested cards; flatten with spacing, typography, and dividers
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "DOM scan: no card-styled container (border + radius + background)
      nested inside another"
    waiver: documented
    fails_when:
      - cards inside cards inside cards
  - id: SLP-5
    source: DX-DS
    title: No icon-tile-above-heading feature-card template; no identical card grids
      as default layout
    tier: L2
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
    verify: "Layout scan: no icon-tile-above-heading card template; no grid of
      identical cards as the default page layout"
    waiver: rationale
    fails_when:
      - the universal AI feature-card shape repeated in a grid
  - id: SLP-6
    source: DX-DS
    title: Adjacent type-scale steps differ by at least 1.10x; no flat hierarchy
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
    verify: Adjacent steps used on the page differ by ratio >= 1.10; flag flat
      heading/subheading/body hierarchies
    waiver: rationale
    fails_when:
      - heading/subheading/body at nearly the same size
  - id: SLP-7
    source: DX-DS
    title: Spacing has rhythm — related items grouped tighter than unrelated ones
    tier: L2
    check: judgment
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Spacing scan: related items grouped tighter than unrelated; flag a
      single spacing value used uniformly"
    waiver: rationale
    fails_when:
      - one spacing value used everywhere
  - id: SLP-8
    source: DX-DS
    title: No bounce or elastic easing on interface elements
    tier: L1
    check: deterministic
    phase:
      - implement
      - verify
    applies_to:
      - component
    verify: "Easing scan: no bounce/elastic/overshoot easing (spring overshoot,
      cubic-bezier with y outside [0,1]) on interface elements"
    waiver: documented
    fails_when:
      - dialogs that spring in with overshoot
  - id: SLP-9
    source: DX-DS
    title: Copy carries no AI-writing tells — marketing buzzwords, em-dash chains,
      filler, or redundant label/helper pairs
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - content
    verify: Lint for the buzzword, filler, and chatbot-artifact lists and em-dash
      chains; evaluator judges structural tells (negative parallelism, forced
      triads, copula avoidance, significance inflation), redundant label/helper
      pairs, and marketing tone
    waiver: rationale
    fails_when:
      - streamline/empower/supercharge/world-class
      - label, sublabel, and helper saying the same thing
      - chatbot artifacts ("Great question!", "I hope this helps")
      - "\"It's not just X, it's Y\" / \"serves as a\" / forced triads doing
        rhetoric, not enumeration"
    enforced: partial
    script: checks/content-lint.py
  - id: SLP-10
    source: DX-DS
    title: Complex multi-section tasks get a page, not a modal
    tier: L1
    check: judgment
    phase:
      - intent
      - plan
      - verify
    applies_to:
      - flow
      - page
    verify: "Evaluator: any task with multiple sections, its own scrolling, or a
      column layout gets a page, not a modal"
    waiver: documented
    fails_when:
      - modal with its own scrollbar and column layout
  - id: SLP-11
    source: DX-DS
    title: A card is only for an interactive unit (clickable, selectable,
      draggable); static content is grouped with spacing, type, and dividers
    tier: L2
    check: judgment
    phase:
      - plan
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Evaluator: every card-styled container (border or shadow + radius +
      background) is a discrete interactive unit; if removing the card chrome
      would not hurt comprehension, it should not be a card"
    waiver: rationale
    fails_when:
      - static content boxed in card chrome purely for visual separation
      - a card used as a section wrapper where a heading plus spacing would
        group it
  - id: LAY-1
    source: DX-DS
    title: Layout uses the product's declared column grid and gutter scale; ad-hoc
      column counts and off-scale gutters are findings — N/A where no grid is
      declared
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: Where a product declares a grid (.dx/design.json layout_system),
      gutter/margin values resolve to the TOK-2 spacing scale and column count
      matches the declared value; evaluator judges whether the column structure
      reads as coherent; grades N/A where no grid is declared; no script, per
      this control's accepted gap
    waiver: rationale
    fails_when:
      - a declared grid exists and the layout uses an ad-hoc column count not
        drawn from it
      - a declared grid exists and gutters/margins don't match the spacing scale
        (TOK-2) with no deliberate reason
  - id: LAY-2
    source: DX-DS
    title: Layout reflows to a single column with no loss at 320 CSS px (WCAG 2.2 SC
      1.4.10); reading order and controls hold at 360/768/1280
    tier: L1
    check: judgment
    phase:
      - verify
    applies_to:
      - page
    verify: Evaluator confirms single-column reflow with no two-dimensional
      scrolling at 320 CSS px (= 1280 at 400% zoom) and a usable reading order
      at each captured width
    waiver: documented
    fails_when:
      - content or controls become unreachable or ambiguous at 320 px reflow
      - reading order reverses or a primary action disappears at the narrowest
        width
  - id: LAY-4
    source: DX-DS
    title: Body-text columns cap at a comfortable measure — target ~66ch, never
      above 80ch (WCAG 1.4.8); full-bleed running text is a finding
    tier: L2
    check: hybrid
    phase:
      - implement
      - verify
    applies_to:
      - page
      - component
    verify: "Prose/body containers carry a max-width and the measure is <= 80ch
      (target ~66ch); checks/type-scan measure rule (built in #161) judges ch
      caps already present, and the evaluator judges the rendered measure"
    waiver: rationale
    fails_when:
      - body-text column has no max-width and spans the full viewport at 1280
      - measure exceeds 80ch
    enforced: partial
    script: checks/type-scan.py
  - id: LAY-3
    source: DX-DS
    title: A surface maps to a known page template (workspace view, form, flow step,
      dashboard, settings, empty state, onboarding) rather than an ad-hoc shell
    tier: L2
    check: judgment
    phase:
      - plan
      - verify
    applies_to:
      - page
    verify: Evaluator matches the surface to its declared page type (Phase 1) and
      flags a bespoke shell where a known template applies
    waiver: rationale
    fails_when:
      - a surface invents a bespoke shell structure when an existing template
        applies
      - information hierarchy does not match the declared page type
  - id: LAY-5
    source: DX-DS
    title: Density suits the task — neither cramped for data entry nor padded for
      scanning; extreme outliers in either direction are findings
    tier: L2
    check: judgment
    phase:
      - verify
    applies_to:
      - page
      - component
    verify: Evaluator judges density against the page type and task (a data-entry
      form and a summary dashboard have legitimately different densities)
    waiver: rationale
    fails_when:
      - a data-entry surface is too padded for efficient tab traversal
      - a reading or scanning surface is too cramped to scan without error
  - id: LAY-6
    source: DX-DS
    title: Shared edges align; optical alignment is used where geometry misleads
    tier: L2
    check: judgment
    phase:
      - verify
    applies_to:
      - page
      - component
    verify: Evaluator checks edge alignment in the 1280 screenshot; flags shared
      edges that do not align without a deliberate reason; distinguishes
      geometric from optical alignment (an icon optically centred but
      geometrically offset is correct)
    waiver: rationale
    fails_when:
      - shared edges between sections or components do not align, with no
        deliberate reason
      - text or icon baselines and optical centres are visibly misaligned
  - id: LAY-7
    source: DX-DS
    title: The page has one primary focal region and its visual reading order
      matches the task's priority order
    tier: L2
    check: judgment
    phase:
      - plan
      - verify
    applies_to:
      - page
    verify: Evaluator applies the squint test (does the first-glance read land on
      the region the task needs done first?) and enumerates the page's distinct
      visual regions by weight; a page with two or more regions of comparable
      weight and no task reason for the tie, or whose first-read region is not
      the task's priority region, fails
    waiver: rationale
    fails_when:
      - two or more regions compete for first read with no task reason for the
        tie
      - the squint-test first-read lands on secondary content
      - the primary action's region is visually subordinate to decoration
```
