A design system isn't done at launch — it's done when it stays right under pressure. This page is the public artifact of every maintenance pass since v1.0 shipped — each finding, decision, and fix, in the open. Newest first.
Cumulative totals · all releasesv1.0 → v1.5 · latest pass Reserve green (semantic-color pass)
Findings logged
18
Across a11y, motion, contrast, naming, color, and architecture.
Fixes shipped
15
Tokens, component patches, and a three-tier architecture.
Deferred
3
Out-of-scope or needs broader RFC.
Breaking changes
0
Every v1.0 class still renders.
Why this page exists
Most of the systems I work on live behind NDAs. I can't show the dashboards, the monitoring panels, or the operator tools that pay the bills. Tiny Wire is the public-safe twin — same constraints, same density, same warm operational tone — built so the process is visible without leaking a single line of client work.
This audit is the proof. It shows what shipping a v1.0 and then maintaining it actually looks like: catching the regressions, defending the decisions, and writing down enough context that the next person (or the next quarter's me) doesn't have to relitigate them.
v1.5 — Reserve green
2026-06-14 · semantic-color pass, zero breaking changes (back-compat aliases kept).
Building a conceptual attestation prototype on the system surfaced the finding the manual eye had normalized: green was doing double duty — the brand accent and the semantic "healthy / attested / safe" signal. When the brand mark, primary buttons, focus rings, and the "this is safe" chip all share one green, the trust signal stops popping. v1.5 reserves green strictly for a new --success family (attested / approved / safe / healthy / running) and moves brand + all interaction onto the existing cobalt — so the rarer green now reads as a real verdict. Token names are unchanged and the old .alert-brand / .banner-brand / .tag-brand remain as aliases, so the prototype inherited the entire shift through the semantic tokens — no per-screen edits. A free win fell out of it: moving brand off green closed the longest-deferred a11y finding (brand-on-white text, 3.54:1 → cobalt 8.3:1).
ID
Finding
Severity
Status
F-017
Green carried both the brand accent and the "safe / attested" status meaning — overloaded, so the trust signal never popped. Reserved green for a new --success family; brand + all interaction moved to cobalt; back-compat aliases kept.
Medium
Fixed
F-018
The green-as-text contrast issue moved with the role: --success (green-500) as text on light tints is still ~3.5:1. Guidance shipped — --success-dark for text, bare --success for non-text (dots, borders, fills); a lint rule against bare --success on text is queued.
Low
Deferred · lint rule
v1.4 — Live demos
2026-06-13 · docs-experience pass, no token or class changes.
A structured UX critique (FLOWIE) found the docs were full of false affordances — demos that looked interactive but did nothing — plus wayfinding gaps and a scroll order that didn't track the nav. All fixed without touching the CSS-only system: the docs gained a thin vanilla-JS layer so every demo is honest (tabs, accordion, popover, command search, dialog/sheet now work), code blocks copy in one click, long pages get an auto table of contents + back-to-top, Foundations gets token search, and every page's scroll order now equals its sidebar order. The orphaned sidebar-rail page and the dead "view diff" links were fixed too. Full list in the changelog.
v1.3 — Pedigree pass
2026-06-12 · additive, zero breaking changes.
A systematic rigor + signature pass. The neutrals consolidated into one coherent warm-neutral ramp; a single cobalt accent replaced the two overlapping blues and now owns focus; the warning brown became a disciplined amber; and the whole set was restructured into a three-tier primitive → semantic → component architecture — values only, every public token name preserved, so downstream prototypes inherit it unbroken. Type moved to a 1.2 modular display scale with em tracking and a mandatory tabular-numerals token; the default radius sharpened 10→8; floating overlays dropped their solid border for a hairline and let elevation do the lifting. Four issues surfaced and were fixed in the same pass:
ID
Finding
Severity
Status
F-013
Dark --text-tertiary failed AA on --surface-inset (4.05:1). Lifted #948682 → #A0928E; the live checker now passes every pair in both themes.
High
Fixed
F-014
lib/tokens.js had drifted from globals.css — three values (light/dark tertiary, dark disabled) shipped pre-AA hexes to JS consumers. Resynced.
Medium
Fixed
F-015
The .select chevron was a hardcoded #8A7E7D (a stale token value) that read low-contrast in dark mode. Repointed to a mid-neutral legible in both themes.
Medium
Fixed
F-016
Floating overlays stacked a solid border and a full elevation shadow — a doubled container signal. Border → --border-hairline; elevation does the lifting.
Low
Fixed
v1.2 — Sidebar rail
2026-06-11 · additive, zero breaking changes.
Added .sidebar--rail — the collapsed, icon-only form of the app sidebar (56px, driven by --sidebar-w). It reuses every existing .sidebar* rule and token: labels hide, icons center and grow to 20px, the active state is unchanged. See the Sidebar component and the rail demo; the full entry lives in the changelog. Scope was the rail only — the deferred a11y findings below remain queued for a later release.
v1.1 — Findings
Twelve issues, grouped by theme. Click a filter to scope the list. Every fixed finding links to the line that changed.
Filter
F-001CriticalAccessibilityMotion
No prefers-reduced-motion support
Fixed
What I found
The monitoring dot ripple, the skeleton shimmer, the modal entrance, the toast slide-up, and the cross-fade on theme change all kept running regardless of OS reduced-motion settings.
A user with a vestibular disorder visiting the docs site would see ambient pulses they can't turn off. This is a WCAG 2.3.3 fail.
The fix
Added a global @media (prefers-reduced-motion: reduce) rule in globals.css that crushes every animation and transition to ~1ms. Components don't need to opt in.
Ambient pulses freeze, entrances become instant, but state changes still happen — just without the choreography.
Try disabling motion in your OS — this dot stops pulsing.
F-002CriticalAccessibility
Focus ring overrode invalid-state borders
Fixed
What I found
The global :focus-visible rule used border-color: var(--brand) !important, which silently overrode the red border on an aria-invalid="true" input the moment a user focused it.
So the only state where the user most needs to know the field is wrong — when they're standing on it — was hiding the signal.
The fix
Dropped the !important border override. The 3px shadow ring (--shadow-focus) is now the universal focus indicator, and components own their own border colour — so invalid inputs stay red whether focused or not.
The link button used --text-tertiary (#8A7E7D on white = 3.85:1 — failing WCAG AA for body copy at 4.5:1). It also visually clashed with disabled-text styling, so users hesitated to click it.
The fix
Switched to --info (#1B4079) — a deep navy that carries link semantics across the system and clears AA comfortably. Hover now lifts to --text-primary with a thicker underline.
v1.0 had a permanent :root * rule that transitioned background-color, border-color, color, fill, and stroke over 220ms — so the cross-fade on theme change would be smooth.
But that rule also ran on every hover, focus, and click — slowing the intended 150ms button hover and making icon colour changes feel laggy.
The fix
Demoted to a one-shot .theme-swapping class the toggle adds on click and removes after 280ms. Theme cross-fade still looks smooth; every other interaction is back to snap.
Toggle the theme — notice the swap still feels deliberate, but button hovers are now instant.
In dark mode, --text-disabled (#5F5450) on --surface-muted (#2A211E) landed at ~2.3:1. Disabled buttons looked empty. Even users without low vision struggled to read the label.
The fix
Lightened to #756763 (~4.1:1) — still visibly de-emphasised, but the label survives. Light-mode --text-disabled already passed; left alone.
--text-tertiary (#8A7E7D on white) landed at 3.85:1 — fine for UI elements, but the same token was being used for helper text and placeholders, where AA wants 4.5:1 for normal body copy.
The fix
Darkened one step to #7A6E6D (4.65:1). Visually still reads as a third-tier label; functionally now AA-compliant. Cascades to every helper, hint, secondary nav link, and breadcrumb.
Both controls killed their default outline in v1.0 but never added a custom ring. The global :focus-visible shadow was clipped by the switch's own background fill, and never reached the slider thumb at all. Keyboard users couldn't tell what was focused.
The fix
Switch now carries a two-stop shadow (surface gap + ring) to clear its background. Slider explicitly applies a 4px ring to both the -webkit- and -moz-range-thumb pseudos.
Banners shipped with danger, brand, and info — but no warning variant. Meanwhile .alert-warn and .chip-warn both exist. A consumer reaching for "banner-warn" by analogy would get nothing.
The fix
Added it. Inherits from the same --warning token; same border + tint formula as the other banner variants.
Maintenance window starts at 02:00 UTC. Estimated 20 minutes.
The .dot-amber class pointed at --warning — which is a warm brown, not amber. Anyone naming a status by colour and getting brown out is going to file a bug.
The fix
Added a semantic .dot-warn for the brown, and re-pointed .dot-amber at the actual --chart-amber token (#B98326). Existing consumers using .dot-amber will see a colour shift — flagged in the changelog as a deprecation.
The "header" checkbox in a data table — the one that's neither fully checked nor fully empty when some rows are selected — had no visual treatment. Native browsers render a dash; v1.0 didn't style it.
The fix
Added .checkbox:indeterminate with a centred 8×2 white dash on the brand-green fill. Matches the checked state's visual weight.
Toast / spinner / skeleton ARIA hooks not documented
Deferred · backlog
What I found
Live-region components ship as CSS only — no role="status", aria-live, or aria-busy guidance in the docs. Consumers who copy the markup verbatim ship inaccessible feedback.
Why deferred
Adding placeholder selectors in CSS would lie about coverage. The real fix is doc updates with full examples per component — a writing pass, not a styling pass. On the backlog alongside the toast queue manager.
Tracking issue: #TW-014.
docs/components.html · backlog
F-012MediumAccessibility
Dialog has no focus-trap implementation
Deferred · v2.0
What I found
Tab order escapes the dialog into the page behind it. The dialog also doesn't restore focus to the trigger on close. Without JS, this is unavoidable — v1.0 is CSS-only by design.
Why deferred
Fixing it properly means shipping a small JS controller (data-dialog-trigger + data-dialog-target), which is a v2.0 question: do we want Tiny Wire to have behaviour, or stay CSS-only? Drafting an RFC instead of shipping something half-baked.
RFC · TW-018 (drafting)
How I audit
Three passes, three goals. I run this same loop on every system I maintain, internal or public.
01
Read the contract
Walk every component and ask: what does the class promise? Every CSS rule that contradicts the promise — silent overrides, missing states, name/value drift — gets logged.
02
Stress the edges
Tab through every interactive element. Toggle to dark mode mid-interaction. Trigger every aria-invalid, disabled, and indeterminate. The edges are where v1.0 leaks.
03
Defend what stays
Not every finding is fixed in the same release. Deferred items get a tracking ID, a scheduled version, and a reason — so the next quarter's me doesn't relitigate them from scratch.
What I won't ship (and why)
A design system is as much about what you refuse to add as what's in. These came up during the audit and stayed out.
A third type weight ramp
Bricolage + DM Sans is the system. Adding a mono display family "for code-forward dashboards" was floated and rejected — every font is a maintenance contract.
A separate "compact" density
Tiny Wire is already dense by default (13px base, 32px controls). A "compact" override would double the surface area of every state matrix. If 13px is too big, override the token at scope.
Hand-drawn iconography
Examples use Lucide-style strokes inline. The system doesn't ship icons — bundling a set means owning a set. Consumers pick their own.
A second brand colour
The temptation is real — every operational tool has a "secondary accent" moment. But every additional brand-mapped token doubles the override matrix for re-skinning. Override --brand at the consumer's scope instead.
What's next
The next release is in flight. The deferred findings get their fix, plus the additions below.
Target
Type
Notes
Status
ARIA documentation pass
Docs
Toast, spinner, skeleton, alert — one block per component on the components page.
In progress
Segmented control
New component
A formal name for what .btn-group + .btn.active is doing. Comes with keyboard arrow nav.
Drafting
Date / range picker
New component
The most-requested gap from the v1.0 feedback round.
Drafting
Theme builder
Tooling
Live token editor in the docs site. Drop a brand colour, see every component re-skin.
Backlog
Focus-trap RFC (TW-018)
Spec
The "does Tiny Wire ship JS?" decision. Drafting.
Backlog
Every release ships with a page like this. The point isn't the changelog — every repo has one of those. The point is to make the reasoning visible: what changed, what didn't, and what I refused to add.