A tab list broke in a DeFi frontend: the markup pointed aria-controls="panel-send" at an element that didn't exist, and arrow-key navigation didn't work at all — a manual role="tablist" bolted onto a Radix Tabs component that already had its own working implementation sitting one import away (GitHub). This is exactly the trap a Tailwind-and-ARIA guide warns against: utility classes make an interface look finished, but without semantic structure and proper attribute wiring underneath, that polish is cosmetic for keyboard and screen-reader users (bahaj.dev). Nobody noticed the break until an accessibility audit flagged it. Then the issue sat open for days until a contributor in a bounty program — the "Stellar Wave Program," paid in points redeemable against a reward pool — picked it up, got assigned a due date, and closed it out for credit (GitHub).
That's not a horror story about one sloppy team. It's the default shape of ARIA compliance at most startups: nobody owns it until an automated scan or an outside contributor is paid to.
Radix Owns Less of Your Surface Than You Think
The useful counterexample comes from a different repo, where a team working through accessibility test debt drew an explicit line: once an axe scan is running in CI, stop re-proving behavior that a Radix primitive already owns — focus trapping, Tab cycling, Escape dismissal, focus returning to the trigger, the aria-hidden wiring between trigger and content (GitHub). What's left for the product's own test suite is narrower: does this specific surface open from this specific trigger, show this specific content, and respect what the product itself controls.
That split matters because it forces a decision most teams never make explicitly: which ARIA behaviors belong to the primitive vendor, and which belong to you. Skip that decision and you get the DeFi tab list — someone re-implements what Radix already does, badly, because nobody drew the boundary and the duplication felt safer than trusting a dependency. A review of an LLM-oriented component skill set makes the same point from a different angle: asking a model to "build an accessible modal" reliably produces plausible-looking ARIA attributes that still trap VoiceOver users, because syntactic correctness and runtime behavior are not the same thing — the fix there was pairing every pattern with an executable gate, not a prompt instruction (DEV Community).
The uncomfortable part is that "trust the primitive" only works if you actually stay on the primitive. The moment someone reaches for a manual role="tablist" because the Radix API didn't fit some edge case, you've silently reassigned ARIA ownership to whoever wrote that one component — and that person is very unlikely to be thinking about the WAI-ARIA tabs pattern when they do it.
Compliance by Audit Is Compliance by Accident
The tab-list bug is a useful case study precisely because of how it got fixed: not by an owner, but by a gig contributor working against a point system and a due date (GitHub). That's a perfectly fine way to clear a backlog item. It's a bad way to run accessibility governance, because the next regression in that codebase has no guarantee of landing in front of the same kind of attention. The audit found the bug; the bounty program cleared it; nothing in the system prevents the next manual ARIA implementation from shipping the same way.
A parallel pattern shows up with contrast tokens. One practitioner's writeup on baking WCAG thresholds into a token pipeline makes the same point from the token side: a design token file that stores color values but not color relationships can't catch a label-on-card pairing that drops under 4.5:1, no matter how disciplined your designers are — low-contrast text was the single most common failure found across a million home pages scanned in 2026, per the same analysis — so the fix was a script that fails CI when a token pair violates the ratio, rather than relying on a manual audit to catch it later (Art of Styleframe). Same failure mode as the tab list: compliance that depends on someone remembering to check, instead of a build step that refuses to let the violation merge.
What Owned Actually Looks Like
For contrast, the fix is a CI gate. For ARIA, it's closer to what a correctly built accordion pattern looks like: heading wrapping button, aria-expanded and aria-controls driving both the announcement and the visual state off the same attribute, so what's announced and what's shown structurally cannot drift apart (CodeFronts). That's not a framework decision — it works whether you're on Radix, a hand-rolled component, or native details/summary, and it echoes the "semantic HTML first" rule that the Tailwind accessibility guide leads with: don't reach for ARIA when a native element already does the job (bahaj.dev). The design decision is making the visual state and the accessible state the same piece of state, instead of two things a developer has to remember to keep in sync.
None of this requires a dedicated accessibility hire. It requires picking, in writing, which ARIA behaviors your primitive layer owns, which your product code owns, and putting a gate — a scan, a lint rule, an executable test — between "it works for me" and merged (DEV Community). The alternative is what you already have: compliance that shows up only when an audit runs, or when someone outside your team gets paid by the point to go find it.
