I've been building with Angular for ten years, and in that time I've watched the same thing happen on nearly every project. A component gets built, it works with a mouse, it ships. Accessibility — keyboard support, focus, ARIA, contrast, the screen-reader story — becomes a line in the backlog called something like "a11y pass". And that ticket sits there. It gets groomed, re-estimated, pulled into a sprint, bumped out of the sprint, and eventually closed as "we'll come back to it." We never came back to it.
For a long time I thought that was a discipline problem. If we just cared more, tested more, remembered more, we'd get it right. I don't believe that anymore. Accessibility keeps losing the race to the deadline not because people don't care, but because it's genuinely hard to hold in your head while you're also wrangling state, layout, data and a ticket that was due yesterday. You cannot fix a systemic problem with individual willpower. You fix it by changing the default.
That realisation is the whole reason I built what I built. This is the story of the problem I was actually trying to solve, and how I went about it.
The problem isn't "not enough components"
Let me be clear about what I was not trying to do, because it matters. I wasn't trying to make another component library. There are plenty. A few new ones have appeared just in the last few months, and they're perfectly good — nicely styled, well documented, quick to drop in.
But open the accessibility story on almost any of them and it's the same shape every time: a compliance checkbox, bolted on near the end, covering the third of WCAG that an automated tool can see. aria-label is present, contrast is fine on the light theme, AXE is green. And then you drive it with a keyboard and the dialog swallows your focus, the custom select doesn't respond to arrow keys, the chart is a blank rectangle to a screen reader, and the dark theme quietly fails contrast because nobody checked.
That's the gap. It's not a shortage of components — it's that in almost every library, accessibility is a feature you're expected to finish yourself, and modern Angular is something the library will get to eventually. I didn't want to add to the pile of components. I wanted to close that gap.
So I made two bets, and the whole thing is built on them.
Bet one: accessibility is a first-class citizen, not a compliance pass
The rule I set myself is simple to state and annoying to keep: no component ships until it works by keyboard and by screen reader. Not "until AXE is green" — AXE is a regression net for the easy third, and I run it in CI on every component, but it is not the test. The test is a human putting the mouse down and driving the whole thing with Tab, Enter, Space and arrows, then turning on VoiceOver or NVDA and listening to whether what they hear matches what's on screen.
In practice that meant solving, once and properly, all the things that normally get skipped:
- Focus management as a discipline — trap it in dialogs and drawers, restore it to the trigger on close, move it to the heading on route change, send it somewhere sane after you delete the row it was sitting on. One position, always accounted for.
-
State that's communicated, not just coloured —
aria-invalidandaria-describedbywiring errors to fields,role="alert"so they announce, live regions that actually exist in the DOM before the text arrives so a screen reader speaks them. - The hardest case, data visualisation — every chart is keyboard-navigable and carries a visually-hidden data table behind the SVG as the screen-reader source of truth, because "this picture holds information that exists nowhere else" is the single biggest blind spot in automated testing. (There's even optional audio sonification for trends, which was two weeks of my life and worth every day.)
- Contrast that survives theming — every colour is a token that re-tones for dark mode and holds AA on both surfaces, verified with an automated contrast pass across every component in light and dark, not eyeballed once on white.
None of that is exotic. It's all well-understood. The point was never to invent new accessibility techniques — it was to do the known-correct thing every single time, so that the person consuming the library never has to file that "a11y pass" ticket, because it's already done.
Bet two: signals-first, because reactive accessibility is better accessibility
The second bet is that this had to be built the modern Angular way — and not for fashion. Standalone components, signals, OnPush everywhere, Material 3 tokens, strict TypeScript, zero NgModules.
Signals aren't just a nicer state primitive here; they turn out to make accessibility easier to get right. When your aria-live announcement, your aria-invalid state, your aria-current and your focus targets are all derived from signals, the accessible state can't drift out of sync with the visual state — they're computed from the same source. Reactive accessibility is more correct accessibility, almost for free. Building on the old change-detection model, you're forever remembering to keep two representations in step. With signals you compute one and both fall out of it.
It also means the library is built the way you're already building in Angular v20+ — so it drops into a modern app without dragging a NgModule-era mental model back in with it. A component library that fights your architecture isn't saving you time.
How I actually went about it
Two bets are easy to write on a slide. Holding them across a real, sellable product took some machinery:
- AXE wired into CI as the automated floor, plus a structural accessibility test suite — the regression net for the third that tools can catch, so nothing slips backwards.
- A browser-driven contrast harness that walks every component in both themes and fails the build on a single AA violation. That's how I know dark mode holds, rather than hoping.
- A token architecture where every surface, text and semantic colour chains through shared design tokens, so theming and dark-mode contrast cascade instead of being re-hardcoded per component.
- Dogfooding, relentlessly. I built the marketing site itself, its FAQ, and three complete example apps — an admin dashboard, a storefront, and a booking diary — entirely out of the packs. If a component was awkward to use or quietly inaccessible, I found out by living in it, not by reading my own docs.
Eight packs, a hundred-and-six components, all held to the same line. Built solo, which is terrifying to admit and also exactly why the defaults had to carry the weight — I can't manually re-check accessibility on a hundred components by hand every release, so it had to be baked in and enforced by tooling.
Why I'm banging this drum
I'm writing this partly because the space is getting busier, and I think the distinction is worth being loud about. More component libraries is good for Angular. But "accessible by default" and "modern by default" are not the headline feature on most of them — they're the footnote. For me they're the entire product. If a green CI check is going to tell you the truth about whether a real person can use what you shipped, the two-thirds that automated tools can't see have to already be handled. That's the thing I built, and it's the thing I'll keep making noise about.
If accessibility is the part of your build that always loses the race to the deadline — the ticket that never gets picked up — that's precisely the problem this is meant to take off your plate.
And it's not finished. Eight packs is where it starts, not where it stops — there are more in the pipeline, on a steady cadence, each held to the same line. Which is also where you come in: I'd genuinely rather build the components you're about to write by hand than guess. So if there's a widget you keep re-implementing, a pack you wish existed, or a gnarly accessibility case you've never seen a library get right — tell me. A lot of what's here started as exactly that kind of "someone should just build this properly" itch, and I'm very open to the next one being yours.
I got tired of watching accessibility lose the race, so I built the defaults instead. NgBracket is a set of Angular component packs — 8 packs, 106 components — where WCAG AA, AXE-passing, keyboard and screen-reader support are the baseline, and everything is signals-first, standalone, OnPush, Material 3 themeable, zero NgModules. We're launching soon; join the waiting list for early access.
Top comments (0)