design-component — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited design-component (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Produce a complete component specification matching the project format.
CLAUDE.md → "Component Guidelines" (quality bar, the 8-state table) and "Atomic Design".components/atoms.md, molecules.md, organisms.md, templates.md, navigation.md, feedback.md, forms-advanced.md, overlays.md. Match the existing spec format.accessibility/aria-patterns.md and contrast/target rules from accessibility/wcag-checklist.md.tokens/*.json) — sizes via sizing.json, states via states.json.taste/design-taste.md (states, focus, no slop).python3 scripts/scaffold_component.py "<Name>" to emit a stub, then fill it in.Spec with: anatomy diagram, variants table, sizes table, all 8 applicable states, token mapping, accessibility (role/keyboard/SR), and a note to render via frameworks/adapter-protocol.md.
A component is only "correct" when every variant × state renders right — not just the resting default. Build a states harness: render the component in each applicable state (default, hover, focus, active, disabled, loading aria-busy, error aria-invalid, selected aria-pressed/aria-selected) × each variant in one HTML file (see examples/component-states/button.html). Then RUN the gates and report their real output (CLAUDE.md → Verification Protocol):
node scripts/verify_states.mjs <harness> [--dark] — contrast of every element in default/hover/focusnode scripts/axe_audit.mjs <harness> [--dark] — ARIA/role/name/label correctnessnode scripts/measure_render.mjs <harness> [--dark] — every text element AAnode scripts/verify_focustrap.mjs <harness> --open=<trigger>Every state must pass in light AND dark before the component is "done". Never claim a state is correct without a gate proving it.
The contrast/axe gates pass while the UI is still visibly broken: a checkbox that doesn't toggle, a dash sitting at the bottom of its box, a checkmark and an indeterminate dash with mismatched stroke weight, a control that's too heavy. You must screenshot the harness and inspect it before claiming done — for every state, and after interaction. Playwright + system Chrome:
const b = await chromium.launch({channel:'chrome'});
const p = await b.newPage({deviceScaleFactor:4});
await p.goto('file://'+abs); await p.addStyleTag({content:'*{transition:none!important}'});
await p.mouse.move(2000,2000); // park pointer OFF the component
await p.locator('.stack').first().screenshot({path:'/tmp/x.png'});Read the PNG. Then look for, specifically:
await loc.click(); expect(await loc.isChecked())). A custom control whose overlay box covers the real <input> will not toggle unless the box has pointer-events:none (or an enclosing <label> forwards the click).display:grid box holds an opacity:0 sibling plus a ::after, the pseudo lands in row 2 → use display:none on the hidden sibling, or one container child.<svg>, two <path> toggled by state — same stroke-width), never an svg check vs a CSS ::after rect (they read as different weights).Consistency across files is non-negotiable. The same component (e.g. checkbox) must use byte-identical CSS + markup in every harness/page. A checkbox that looks thin in form-controls and heavy (native accent-color) in data-table is a bug. Factor one pattern, reuse it verbatim.
Real <input> underneath (keeps native a11y + keyboard); a drawn .box overlay with pointer-events:none; check + dash as two <path> in one <svg> toggled by :checked / :indeterminate; 1.5px border-strong, .25rem radius, .62rem glyph, stroke-width:2 round caps. Reference: examples/component-states/form-controls.html and data-table.html (select-all uses indeterminate). Native accent-color renders too heavy — do not use it when the house look is "thin".
Build mobile-first; a fixed-px width that can't shrink is a bug. Run node scripts/verify_responsive.mjs <file|dir> — it loads each harness at 280/320/414px and fails on any horizontal overflow. The four recurring causes and their fixes:
inline-size:100%;max-inline-size:Npx (cap, don't pin).*{margin:0} reset does NOT clear padding) → padding:0;margin:0 on every list. This also silently mis-aligns a list's edge vs a sibling block (looks like unequal widths) — same fix.flex-wrap:wrap, or for tabs overflow-x:auto + .tab{flex:none}.minmax(min(Npx,100%),1fr).Timing/easing are tokens (--duration-fast|normal|slow, --ease-out|in|in-out|emphasized in the theme; --transition-micro = fast ease-out). Never hardcode ms/curves. A component that toggles open/closed must animate its height, not just rotate a chevron — collapse via hidden/display:none alone reads as "rigid, no transition". Smooth-height pattern (no JS measuring): wrap content in an inner that clips overflow, animate the grid track:
.panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--duration-normal) var(--ease-emphasized)}
.panel.open{grid-template-rows:1fr}
.panel > .inner{overflow:hidden;min-block-size:0}Keep a11y: expand = remove hidden then add .open next frame; collapse = remove .open, set hidden on transitionend. Reference: accordion in examples/component-states/overlays.html. Always honor @media(prefers-reduced-motion:reduce){…transition:none}.
auto-fill keeps empty phantom tracks so 3 cards cluster left with a void on the right; auto-fit collapses empties so cards stretch to fill the row. Always repeat(auto-fit,minmax(min(Npx,100%),1fr)).align-items:stretch on the grid, AND make the shorter panel's body fill — .panel{display:flex;flex-direction:column} + the inner region flex:1. A child sized with block-size:% (e.g. chart bars) needs a definite-height ancestor (a flex:1 box or explicit height), or the % resolves to 0 and the element collapses. Wrap the bar in a flex:1 .barbox and give the bar block-size:% of that.flex:1 capped by max-inline-size stops growing and leaves empty space after the last item. Push the right-hand cluster with margin-inline-start:auto on its first element.display:block so opening the sidebar pushes main down. Reference: examples/component-states/app-shell.html.Hand-approximated SVG path data renders as broken glyphs (a help "?" became a dot; settings became a hamburger). Use verbatim lucide paths, referenced by name via an injected <symbol> sprite — examples/component-states/icons.js defines each icon once and <svg class="ico" aria-hidden="true"><use href="#i-NAME"/></svg> uses it. No per-use path duplication, no network, offline + gate-safe. Add a new icon to icons.js once; never paste raw paths into markup. (Inline lucide is acceptable only if the path is copied verbatim from lucide.) .ico{stroke:currentColor;fill:none;stroke-width:2} — color via currentColor.
A no-text control (carousel dot, kebab, icon button) is held to 3:1 (WCAG 1.4.11), not 4.5 — verify_states applies this automatically when an element has no direct text node. Two traps it catches:
<button> keeps the UA color:buttontext (≈black) regardless of theme → set its color to the actual indicator color and drive the visual via currentColor (e.g. dot is a ::before{background:currentColor}), so the gate measures the real thing.--color-chart-N, --color-surface-brand) invert between light/dark; white text or a teal indicator on them passes in one mode and fails the other. Use dark-aware values (override in [data-theme="dark"]) or stable tokens (--color-action-primary, --color-text-link which adapts) for avatars, active dots, and selected states.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.