---
description: Use when designing, building, reviewing, critiquing, or polishing any Polymodel front-end surface (Dioxus RSX components, routes, CSS, layout, color, type, motion, states, copy). Carries the adapted Impeccable design method for Polymodel's blueprint identity and product register.
polytoken:
  tags: [frontend, design, ui]
---
# Polymodel front-end design

Use this whenever you touch a Polymodel UI surface: a Dioxus component, a route, `assets/styling/*.css`, or any decision about layout, color, type, motion, state, or UX copy. It exists so front-end work ships with intentional craft instead of generic AI-generated defaults.

Announce: "using the frontend-design skill for Polymodel UI work".

**Source and companions.** This skill adapts [Impeccable](https://github.com/pbakaus/impeccable) (Paul Bakaus, Apache-2.0), which builds on Anthropic's `frontend-design` skill — distilled to Polymodel's stack and identity rather than vendored wholesale. Companions:

- **`DESIGN.md`** (repo root) — the blueprint design system as machine-readable tokens (Stitch format) mirroring `assets/styling/theme.css`. Source of truth for the deterministic detector's design-system checks. Keep it in sync with `theme.css`.
- **`tools/design-detector/`** — the vendored deterministic linter (see Detector below).
- **Confluence**, space `PM`, [*Front-end design guidance*](https://radiant-industries.atlassian.net/wiki/spaces/PM/pages/655362) (child of the Polymodel Overall Design Plan) — the durable human-facing version of this method, with credit and links.

Read `DESIGN.md` and the relevant `assets/styling/*.css` before non-trivial UI work.

---

## 1. Setup: orient before you design

1. **Read the tokens and the existing vocabulary.** `theme.css` (color, fonts, spacing, radius, dark mode), then `base.css` and `cards.css` (cards, buttons, inputs, tabs, status pills, skeletons, the drafting-grid media). Reuse what exists; do not invent a parallel system. `DESIGN.md` documents these as a system with named rules.
2. **Know the register.** Polymodel is **product UI** — a tool people browse and publish with — not a marketing/brand surface. Design *serves* the task.
3. **Preserve the blueprint identity** (§3). It is committed and distinctive. Do not "fix" it toward generic flat-SaaS or dark-terminal defaults.

---

## 2. The register: product, not brand

Every design task is either **brand** (marketing/landing/portfolio — design *is* the product) or **product** (app UI, tools, dashboards — design *serves* the product). Polymodel is product.

The product test is not "would someone say AI made this" — familiarity is a feature here. The test is: **would a user fluent in the category's best tools (Printables, Linear, Figma, Stripe) sit down and trust this, or pause at every subtly-off component?** Product UI's failure mode isn't flatness, it's *strangeness without purpose*: over-decorated buttons, mismatched controls, gratuitous motion, display fonts where labels belong, invented affordances for standard tasks. The bar is **earned familiarity**; the tool should disappear into the task.

Product can afford things brand can't: familiar sans defaults, standard nav patterns, density when users need it, consistency over surprise. Save delight for moments, not pages.

---

## 3. The blueprint identity (committed — preserve it)

Polymodel's north star is **"The Drafting Table"**: a well-kept set of engineering blueprints rendered for the web. These are intentional choices, not slop to clean up.

- **Paper base, not "AI cream."** `--color-base` (`#f5f1e8` light) is a drafting-paper tone tinted toward the brand — the blueprint substrate. This is a *committed* identity choice; identity-preservation beats the generic "never warm-neutral body bg" rule. Never rename it `--paper`/`--cream`/`--sand`; never swap it for a flat off-white.
- **Teal primary, one warm-orange action.** `--color-primary` teal carries structure (active/selected/focus, grid lines). `--color-action` orange is the single warm accent, reserved for the *one* primary action per view. Accent marks action and state, never decoration.
- **Near-square corners.** `--radius-panel: 2px`; controls render at 0px (`base.css`). Never round cards/inputs to 12px+; full pill only for tags. Over-rounding breaks the drafting feel.
- **Drafting-grid media.** Empty/preview surfaces use the grid background (`--color-grid-line`), and missing media draws nested registration rectangles — never a flat gray box, never fake content.
- **Mono for metadata.** `--font-mono` (Ioskeley Mono) carries dimensions, formats, DIDs/handles, counts, status pills. `--font-ui`/`--font-heading` (Proza Libre) carries everything else — one humanist sans for body, headings, labels, buttons.
- **Both schemes first-class.** Every token has a dark variant; verify light *and* dark.

---

## 4. General rules

### Color
- **Verify contrast.** Body ≥ 4.5:1; large/bold-large (≥18px / bold ≥14px) ≥ 3:1; placeholders ≥ 4.5:1 (not muted-gray). The single most common AI failure is muted gray body text on a tinted near-white — exactly our paper base. When a value is even close, move toward `--color-text`/`--color-emphasis`, not `--color-muted`.
- **No gray on color.** Gray text on a colored background looks washed out — use a darker shade of that hue, or a transparency of the text color.
- **Restrained palette.** Tinted neutrals + the action accent. Heavy/full-saturation color does not belong on inactive states. A single surface may earn a committed color, but restrained is the product floor.
- **State vocabulary, standardized:** hover, focus, active, disabled, selected, loading, error, warning, success, info. Accent is for primary action / current selection / state indicators only.

### Typography
- **One UI family.** Proza Libre for headings, labels, body, buttons. No display/body pairing. Mono only for the metadata roles in §3. Never a display font in labels, buttons, or data.
- **Fixed rem scale, not fluid `clamp()` headings.** Product UI is viewed at consistent DPI; a heading that shrinks in a panel looks worse. Tight step ratio (~1.125–1.2). The card-title `clamp()` is the one sanctioned exception.
- **Prose line length 65–75ch.** Dense data/tables may run wider.
- **`text-wrap: balance`** on headings; `pretty` on long prose.

### Layout
- **Flexbox for 1D, Grid for 2D.** Don't reach for Grid where `flex-wrap` is simpler. Responsive card grids without breakpoints: `repeat(auto-fit, minmax(280px, 1fr))`. **Repo trap (AGENTS.md):** a Grid container + `min-height: 100vh` stretches rows and opens large gaps — use a flex column for vertical page shells.
- **Cards are the lazy answer; nested cards are always wrong.** Use a card only when it's genuinely the best affordance. Thing cards are legitimate; a card wrapping cards is not.
- **Vary spacing for rhythm.** Build a semantic z-index scale (dropdown → sticky → modal → toast → tooltip); never `999`/`9999`.
- **Responsive behavior is structural** (collapse nav, responsive tables, breakpoint columns), not fluid typography.

### Motion
- **Conveys state, not decoration.** 150–250 ms, ease-out (quart/quint/expo) — no bounce, no elastic. Reserve it for state change, feedback, loading, upload progress, publish-state, viewer readiness. No orchestrated page-load sequences; users load into a task.
- **`prefers-reduced-motion` is not optional** — every animation needs a reduce fallback (crossfade or instant).
- **Don't animate layout properties** or `<img>` on hover; animate background/border/shadow instead.

### Components & states
- **Every interactive component ships all states:** default, hover, focus, active, disabled, loading, error. Don't ship half. Focus must be visible (the input `:focus` outline in `cards.css`).
- **Skeletons, not center spinners,** for content loading (`.skeleton-*` exist).
- **Empty states teach** the interface ("publish your first thing" + CTA), never "nothing here."
- **Consistent affordances** across browse/search/profile/thing/publish: same button shape, same control vocabulary, same icon style. If "save" looks different in two places, one is wrong.

### Interaction
- Dropdowns with `position: absolute` inside `overflow: hidden/auto` get clipped — use `position: fixed`, the popover/`<dialog>` API, or a portal.
- Exhaust inline/progressive alternatives before a modal; modal-as-first-thought is usually laziness.

---

## 5. Anti-pattern catalog (rewrite if you catch yourself writing these)

Cross-register slop tells:

- **Side-stripe borders** — `border-left/right` >1px as a colored accent. Use full borders or background tints.
- **Gradient text** (`background-clip: text` + gradient). Solid color; emphasis via weight/size.
- **Glassmorphism as default** decoration.
- **The hero-metric template** (big number + label + gradient) and **identical icon-card grids**.
- **An uppercase tracked eyebrow above every section**, or numbered `01/02/03` markers as default scaffolding. (`.eyebrow`/`.product-kicker` are for *deliberate, occasional* use — the ban is reflexive repetition.)
- **Text that overflows its container** at any breakpoint — test thing names/titles at mobile/tablet.
- **Ghost-card**: a 1px border + a wide (≥16px) soft shadow on the same element. Pick one.
- **Over-rounding** (24px+ on cards), **sketchy/hand-drawn SVG**, **repeating-stripe backgrounds**.

Product-specific bans:

- **Decorative motion** that doesn't convey state.
- **Inconsistent component vocabulary** across screens.
- **Display fonts in UI** labels/buttons/data.
- **Reinvented standard affordances** for flavor (custom scrollbars beyond our thin styling, weird form controls, non-standard modals).
- **Heavy/full-saturation accents on inactive states.**

### The slop test (two altitudes)
1. **First-order:** could someone guess the theme + palette from the category alone ("atproto 3D-model site")? If yes, it's the training-data reflex.
2. **Second-order:** could they guess it from category-plus-anti-reference ("not flat-SaaS → must be dark-terminal")? If yes, it's the trap one tier deeper.

Polymodel's answer to both is the **blueprint identity** — lean into it deliberately rather than landing on flat-SaaS or dark-terminal.

---

## 6. Evaluation method (review / critique / audit)

When reviewing a surface rather than building one, run two passes and synthesize.

### Design critique (judgment)
Score Nielsen's 10 heuristics 0–4 (be honest — most real UIs land 20–32/40): system-status visibility, match to real world, user control/freedom, consistency, error prevention, recognition-over-recall, flexibility/efficiency, aesthetic/minimalist, error recovery, help. Then:
- **Cognitive load:** ≤4 items per decision point (Miller/Cowan); chunk, group by proximity, progressive disclosure; one primary action + 1–2 secondary, rest in a menu. Flag any decision point with >4 visible options.
- **Personas:** walk the primary action as 2–3 relevant archetypes (e.g. the impatient power user who wants keyboard/bulk paths; the first-timer who needs visible labels and plain-language errors). Report what *broke* for each, specifically.
- **AI-slop verdict** against §5.

Tag each issue P0 (blocks task) / P1 (major, fix before release) / P2 (minor) / P3 (polish). Be specific ("the publish button," not "some elements") and say why it matters to users.

### Technical audit (measurable)
Score 0–4 across: **accessibility** (contrast, ARIA, keyboard nav/focus, semantic HTML, alt text, labeled inputs), **performance** (layout thrash, expensive/unbounded animations, missing lazy-load/memoization), **theming** (hard-coded colors vs tokens, dark-mode parity), **responsive** (fixed widths, ≥44px touch targets, no horizontal overflow), **anti-patterns** (§5). Run the detector (§7) as the deterministic half of this pass.

---

## 7. The detector

`tools/design-detector/` is the vendored deterministic linter (44 rules; Apache-2.0; see its README):

```sh
node tools/design-detector/detect.mjs --json assets/styling/        # static CSS scan (plain Node)
node tools/design-detector/detect.mjs --json http://localhost:8080  # rendered route (real browser; `just serve` running)
```

Exit `0` clean / `2` findings. It scans **CSS and rendered HTML, not `.rs` RSX** — so it covers `theme.css`/`*.css` and live routes; rely on the §8 checklist for RSX-level structure. `design-system-*` findings (radius/color outside scale) read repo-root `DESIGN.md`, so keep that synced with `theme.css`. Scratch lands in `.impeccable/` (gitignored). Treat `advisory` findings as judgment calls; fix `warning` findings or justify them.

---

## 8. Workflow & checklist

Before review or handoff: run `just fix` and the narrowest relevant tests (`just test` / e2e; `just test-all` for substantial UI changes). Verify in-browser with Playwright MCP — screenshots **plus** accessibility snapshots with boxes and computed-style inspection (screenshots show symptoms; boxes/computed styles find the offending rule). Check **both schemes** and **mobile + desktop**. Don't commit `.playwright-mcp/` scratch.

- [ ] Reused existing tokens/components; no parallel system invented.
- [ ] Blueprint identity preserved (paper base, near-square corners, teal/one-orange-action, mono metadata, grid-line media).
- [ ] Body/placeholder contrast ≥ 4.5:1 in both schemes; no gray-on-tint mud.
- [ ] All interactive states present (default/hover/focus/active/disabled/loading/error); focus visible.
- [ ] Skeleton loading + teaching empty state + actionable error state.
- [ ] One UI family + mono for metadata only; no display fonts in UI; fixed rem heading scale.
- [ ] No §5 anti-patterns; motion conveys state, quick, ease-out, with reduced-motion fallback.
- [ ] Headings don't overflow at any breakpoint.
- [ ] Detector run on changed CSS / affected route; findings fixed or justified.
- [ ] Verified light/dark + mobile/desktop in-browser.
