Sign in

Duncan Faulkner

@duncanfaulkner.bsky.social
116 followers 168 following 284 posts

Accessible Angular components, built to support WCAG 2.2 AA. Checked with axe + automated NVDA tests in CI. Free OSS a11y tools for Angular. ngbracket.com

PostsRepliesMedia
Duncan Faulkner @duncanfaulkner.bsky.social · 21h
The rule → ngbracket.com/tools/a11y-...
ngbracket.com
ngbr/modal-focus-not-contained — @ngbracket/a11y-devtools docs
A modal dialog that doesn't keep keyboard focus inside it. What the finding means and how to fix it.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 21h
aria-modal="true" is a promise, not a behaviour. It hides the page from screen readers but doesn't stop Tab walking out of the dialog. Fix: native <dialog> + showModal(), inert on the background, or a real focus trap.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 08/10/2026
Keyboard & focus docs → ngbracket.com/tools/a11y-...
ngbracket.com
Keyboard & Focus Mode — @ngbracket/a11y-devtools docs
Check keyboard operability — the part axe can't test — with tab-order visualisation, keyboard findings, an accessibility-tree preview, and keyboard-trap detection.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 08/10/2026
axe checks markup; the worst keyboard bugs are behaviour. Our Angular devtool draws the tab order on the page, shows the focused control's computed role and name as you Tab, and runs 4 keyboard checks of its own. Heuristics, labelled as such.
The a11y-devtools keyboard layer on the public demo page, dark theme. Numbered teal badges mark each tab stop, joined by a dashed line; a button with tabindex="3" is badged 1 in orange because it jumps ahead in the tab order. Labelled boxes flag findings from the A11yDemo component: ngbr/unreachable-control, ngbr/click-without-key and tabindex. In the bottom right, a Focus preview card labelled "computed approximation" shows the focused Menu button: role button, its accessible name, states collapsed and has menu popup, component A11yDemo.
110
Duncan Faulkner @duncanfaulkner.bsky.social · 08/10/2026
The rule, with its exact thresholds → ngbracket.com/tools/a11y-...
ngbracket.com
ngbr/tab-order-mismatch — @ngbracket/a11y-devtools docs
Tab moves against the visual reading order. What the finding means, how it's detected, and how to fix it.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 08/10/2026
Press Tab. If focus jumps back up the page, your tab order disagrees with your layout. Usual causes: a positive tabindex, or CSS order / row-reverse / grid moving things visually but not in the DOM. Fix the DOM order, not the tabindex.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 07/10/2026
How attribution works → ngbracket.com/tools/a11y-...
ngbracket.com
How attribution works — @ngbracket/a11y-devtools docs
How a DOM element flagged by axe is traced back to the Angular component that rendered it.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 07/10/2026
Most a11y scanners tell you where a problem is. Ours says whose it is. Instead of .card__head > h3 you get "NgbrCard — heading-order". On our own apps that was the Card hard-coding an h3, breaking every screen that used it. Fix it once, not per page.
Browser console output from @ngbracket/a11y-devtools on an Angular example app. Summary line: "a11y-devtools: 5 issue(s) across 3 component(s)", then findings grouped by the component that rendered each one. A11yDemo, 3 issues: serious color-contrast on .low-contrast; minor empty-heading on h2; critical image-alt on img. (unknown component), 1 issue: moderate landmark-one-main on html. App, 1 issue: moderate region on app-root. Each finding links to its rule reference.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 07/10/2026
The rule, and when it's not a problem → ngbracket.com/tools/a11y-...
ngbracket.com
ngbr/click-without-key — @ngbracket/a11y-devtools docs
A control you can Tab to but may not be able to activate with the keyboard. What the finding means and how to fix it.
010
Duncan Faulkner @duncanfaulkner.bsky.social · 07/10/2026
Tab reaches it. Enter does nothing. A (click) on a non-native element with tabindex="0": browsers only turn Enter/Space into click on real buttons and links. Fix: make it a <button>, or handle keydown for Enter and Space.
110
Duncan Faulkner @duncanfaulkner.bsky.social · 06/10/2026
Live demo → a11y-demo.ngbracket.com/a11y-demo
000
Duncan Faulkner @duncanfaulkner.bsky.social · 06/10/2026
You can now try our Angular a11y devtool in the browser, no install. A live admin app with a broken page and the overlay on: each finding labelled with the component that rendered it, tab-order badges as you press Tab, our own keyboard checks. Open source, dev-only.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 06/10/2026
The rule, with what triggers it and how to check it yourself → ngbracket.com/tools/a11y-...
ngbracket.com
ngbr/unreachable-control — @ngbracket/a11y-devtools docs
A control that the keyboard can't reach. What the finding means, who it affects, how to fix and check it.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 06/10/2026
A div with role="button" sounds like a button to a screen reader, and Tab skips straight past it. Most common keyboard bug in Angular templates: (click) on a div/span, or a role with no tabindex. Mouse users never notice. Fix: a real <button>.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 05/10/2026
3 packs free to start → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 05/10/2026
19+ accessible Angular packs, built in public. What worked: • a11y as a release gate, not a phase • one system, not a bag of widgets • evidence over adjectives (axe + 38 aria-tree snapshots + real NVDA in CI + public conformance report) What I'd redo: never make a claim you can't back.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 05/10/2026
Recipe → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 05/10/2026
Toasts are the component that looks accessible and says nothing. Right way: a live region already in the DOM (polite; assertive only for errors), render the text INTO it, don't auto-dismiss critical ones (WCAG 2.2.1), give a close button, don't trap focus. "Saved ✓" nobody hears isn't feedback.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 02/10/2026
More WCAG wins → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 02/10/2026
WCAG 2.2 rule drag-and-drop UIs miss: Dragging Movements (2.5.7, AA). Any drag function needs a single-pointer alternative that isn't a drag — up/down buttons, a "move to…" menu, tap-then-tap. Kanban, sortable lists, sliders. Drag is an enhancement, never the only way.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 01/10/2026
Ours, criterion by criterion → ngbracket.com/accessibili...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 01/10/2026
Buying an Angular component library you'll defend in an audit? Ask for, in writing: • a published VPAT/ACR (not "on request") • evidence beyond axe (~a third of WCAG) • keyboard as a release gate • contrast in *both* themes • honest caveats • a named WCAG version + level
100
Duncan Faulkner @duncanfaulkner.bsky.social · 01/10/2026
More patterns → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 01/10/2026
Still writing ngOnDestroy + a Subject to unsubscribe? Modern Angular deleted that. obs$.pipe(takeUntilDestroyed()).subscribe() — auto-unsubscribes on destroy. Outside an injection context, pass inject(DestroyRef). DestroyRef.onDestroy(fn) covers imperative cleanup too.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 30/09/2026
One attribute, floor enforced → docs.ngbracket.com/components/...
000
Duncan Faulkner @duncanfaulkner.bsky.social · 30/09/2026
"Can we make it more compact?" is the most common way teams accidentally fail a11y — tighten padding and icon buttons/row actions drop below WCAG 2.5.8's 24×24px target size. NgBracket's density scale tightens spacing but holds a hard ≥24px floor. Dense layout, no motor-a11y regression.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 30/09/2026
Recipe → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
010
Duncan Faulkner @duncanfaulkner.bsky.social · 30/09/2026
A real ARIA menu is stricter than a `<ul>` of links: role=menu/menuitem, ONE tab stop, ↑↓ move (roving), Enter/Space activate, Esc closes + returns focus, trigger has aria-haspopup/expanded, typeahead. A nav bar of links is NOT a menu — that's `<nav>` + a list.
110
Reposted by Duncan Faulkner
Angular @angular.dev · 29/09/2026
Want your UI to be beautiful with accessibility built-in? ✨ Discover how Google Antigravity simplifies creating complex styles with Angular Aria tabs. What’s your biggest accessibility challenge right now? Drop it below👇 Read more: g.dev/ai/customizi...
g.dev
Customizing Angular Aria Tabs Quickly with Google Antigravity CLI
The Angular team has called Angular Aria a set of customizable, fully accessible UI patterns. This is a strong claim, but in practice, how...
122
Duncan Faulkner @duncanfaulkner.bsky.social · 30/09/2026
Announcements that coincide with a focus move. When one action moves focus and posts a live message ("Email added"), NVDA speaks one or the other. Delaying the message made it never spoken. What worked: put it in what focus reads (the new row's name). Caught by running real NVDA in CI.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 29/09/2026
Turn your sound on and try it → docs.ngbracket.com/components/...
000
Duncan Faulkner @duncanfaulkner.bsky.social · 29/09/2026
Charts are where accessibility usually dies — most dataviz libs give a screen-reader user a data table at best. NgBracket Charts: arrow-key point to point, each value announced, and turn on sonification to hear every point as a tone (pitch tracks the value). Hear the *shape* of your data.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 29/09/2026
More WCAG wins → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 29/09/2026
The a11y bug in almost every app: status messages nobody hears (WCAG 4.1.3, AA). "Saved", "3 results", a form error — if it appears without moving focus it needs a live region: polite for most, assertive for errors, present BEFORE you write to it. Toasts, validation, filter counts all count.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 28/09/2026
Our other two-thirds → ngbracket.com/accessibili...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 28/09/2026
Axe catches ~a third of WCAG. What a green run happily ships: a modal that never moves focus into it, "tabs" arrow keys can't traverse, drag-only reorder with no keyboard path, name ≠ visible label. Roles present, behaviour absent. "We pass axe" is a third of the story.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 28/09/2026
More patterns → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 28/09/2026
`computed()` is the most under-used signal primitive. total = computed(() => items().reduce((n,i) => n+i.price, 0)) Lazy, memoised, glitch-free. If a value is *derived*, it's a computed — not an effect that writes a signal. Deriving → computed; reacting (DOM/log/sync) → effect.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 25/09/2026
Recipe → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 25/09/2026
The modal is where focus management dies. A div overlay with a close button isn't a dialog. Real one: role=dialog + aria-modal, labelled by its title, focus moves IN on open, trapped (Tab cycles inside), Esc closes, focus RETURNS to the trigger (most-skipped). Bonus: background inert.
101
Duncan Faulkner @duncanfaulkner.bsky.social · 24/09/2026
We check 304 theme × state combos in CI, both themes → ngbracket.com/accessibili...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 24/09/2026
Ship a dark theme and you've doubled your accessibility surface. Contrast has to hold in *every* theme — a palette that's spotless in light can fail in dark (secondary text, disabled states, focus rings, chart series). One axe run on light mode says nothing about dark.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 24/09/2026
More WCAG wins → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
010
Duncan Faulkner @duncanfaulkner.bsky.social · 24/09/2026
WCAG 2.2 rule sticky headers break: Focus Not Obscured (2.4.11, AA). A focused element must not be *fully* hidden by other content — sticky nav, cookie bar, chat widget. Fix: scroll-margin-top on focusable targets so scroll-into-view clears the sticky bar. Tab through your app and watch.
110
Duncan Faulkner @duncanfaulkner.bsky.social · 23/09/2026
a11y as the default → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 23/09/2026
Accessibility is cheap as a default, expensive as a retrofit. A component library is a multiplier: one inaccessible modal costs every screen that imported it, not one fix. Build it in at the component layer and every screen inherits it — that's the whole ROI argument.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 23/09/2026
More patterns → ngbracket.com/?utm_source...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 23/09/2026
Angular inputs are signals now, and `model()` is the two-way you faked with @Input+@Output. value = model('') // [(value)] disabled = input(false) label = input.required<string>() input() is a read-only signal that drops into computed()/effect(); model() adds .set() + auto valueChange.
100
Duncan Faulkner @duncanfaulkner.bsky.social · 22/09/2026
How we ship auth → ngbracket.com/accessibili...
ngbracket.com
NgBracket — Accessible Angular component packs
Accessible-by-default Angular component packs — WCAG AA and AXE-passing from the start. Buy a pack or subscribe.
000
Duncan Faulkner @duncanfaulkner.bsky.social · 22/09/2026
Your login might be a WCAG 2.2 fail — Accessible Authentication (3.3.8, AA): don't *require* a cognitive test to log in. Let people paste into password + OTP fields (it breaks managers), don't make a puzzle CAPTCHA the only path, support passkeys. "Hardening" the form is how most fail it.
100