web-accessibility-web-accessibility-a76e73 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-accessibility-web-accessibility-a76e73 (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.
Quick Guide: Most of accessibility is settled by choosing the right element: a<button>arrives with focus, keyboard activation and a role that a<div>needs a dozen lines to fake. What the element cannot give you is the rest — a name for every control, a visible focus indicator, 4.5:1 contrast on text, an announcement when content changes, and information never carried by colour alone. Target WCAG 2.2 Level AA. Automated checks find roughly half the failures, so a keyboard pass is not optional.
Detailed Resources:
<critical_requirements>
Reach for the semantic element before the ARIA attribute. <button>, <a href>, <nav> and <table> arrive with a role, keyboard behaviour and focus already correct — every one of which has to be rebuilt by hand on a <div>, and the rebuild is where the failures are.
Make every interactive element reachable and visibly focused. Keyboard users navigate by what the focus ring tells them, so outline: none without a replacement leaves them moving blind through the page.
Give every control a name a screen reader can announce. An icon-only button reads as "button" without one, which tells the listener there is something there and nothing about what it does.
Hold text to 4.5:1 against its background, and UI boundaries and focus indicators to 3:1. Below that the text is unreadable in bright light, on a cheap panel, or to anyone with reduced contrast sensitivity — which is most people eventually.
Pair every colour signal with a shape, an icon or a word. Red-for-error carries nothing to a red-green colour-blind reader, and nothing at all through a screen reader.
Announce content that appears without a page change. A validation error, a saved confirmation and a loaded result are all silent unless they arrive in a live region.
</critical_requirements>
Auto-detection: accessibility, a11y, WCAG, ARIA, aria-label, aria-labelledby, aria-describedby, aria-live, aria-expanded, aria-invalid, role attribute, keyboard navigation, focus management, focus trap, focus-visible, skip link, landmark, screen reader, sr-only, colour contrast, prefers-reduced-motion, tabindex, axe
Applies to:
Handled elsewhere:
<philosophy>
An accessible interface is not a variant of the interface. It is the same one, built out of elements that already carry meaning, with the parts a browser cannot infer stated explicitly.
That reframes most of the work as subtraction. Semantic HTML is the accessible baseline; every <div> that replaces a real element is a debt paid back in ARIA attributes, key handlers and focus management, and paid back badly. The attributes below exist for the cases where no element carries the meaning — not as a layer applied over markup that could have carried it.
Build for the keyboard first. Everything a pointer can do, a keyboard has to do too, and a design that works without a mouse works with one.
</philosophy>
<decision_framework>
What is being built?
├─ Something that acts when activated → <button>
│ └─ Icon only → add an accessible name
├─ Something that navigates → <a href>
│ └─ Current destination → aria-current="page"
├─ A structural region → <nav>, <main>, <header>, <aside>, <footer>
│ └─ More than one of a kind → label each with aria-label
├─ A complex widget — dialog, listbox, tabs, combobox, tree
│ └─ Take a tested primitive. The ARIA contract is large,
│ and a partial implementation reads as broken rather than absent.
└─ Content that appears without navigation
├─ An error or something urgent → role="alert"
└─ Progress or confirmation → role="status"ARIA or not: the first rule of ARIA is not to use ARIA. role="button" on a <div> gives a screen reader the right word and gives the keyboard nothing, so the handlers, tabindex and focus styling are all still owed. Reach for ARIA when no element expresses the thing — a live region, a disclosure's aria-expanded, a sort direction on a column header.
AA or AAA: AA is the compliance target and what regulation generally references. Individual AAA criteria are often cheap — 7:1 contrast, 44×44 targets — and worth taking where the design allows, without committing the whole product to AAA.
</decision_framework>
<patterns>
The first focusable element on the page, hidden until it takes focus, jumping past the navigation every page repeats.
<a href="#main-content" className="skip-link">Skip to main content</a>
…
<main id="main-content" tabIndex={-1}>{children}</main>tabIndex={-1} on the target is what lets focus actually land there; without it the fragment moves the viewport and leaves focus at the top of the document.
Full code: examples/core.md
Landmark elements are how a screen reader user jumps between regions rather than reading linearly.
<header>…</header>
<nav aria-label="Main">…</nav>
<main>…</main>
<aside aria-label="Related">…</aside>
<footer>…</footer>One <main> per page. Where two landmarks share a type, each needs a distinguishing label — two unlabelled <nav>s are announced identically.
Full code: examples/core.md
Activation is a button; navigation is a link. The distinction decides the keyboard behaviour, the announcement, and whether the browser's own affordances work.
<button onClick={handleSubmit}>Submit form</button>
<a href="/dashboard">Go to dashboard</a>A <button> that navigates costs the reader "open in new tab", the status-bar URL and the middle click. A <div onClick> costs them the whole interaction.
Full code: examples/core.md
Every control needs a name, and the icon-only case is where it goes missing.
<button aria-label="Delete item">
<TrashIcon aria-hidden="true" />
</button>
<button>
<TrashIcon aria-hidden="true" />
<span className="sr-only">Delete item</span>
</button>Both announce "Delete item, button". The second survives translation tooling and shows up in a text search of the DOM, so prefer it where either matters.
Resolution order is in reference.md; full code in examples/screen-reader.md.
A label associated with the input, the invalid state on the input, and the message reachable from it.
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-required="true"
aria-invalid={Boolean(error)}
aria-describedby={error ? "email-error" : undefined}
/>
{error && <span id="email-error" role="alert">{error}</span>}A placeholder is not a label: it disappears the moment typing starts, and it fails contrast in most designs.
Full code: examples/forms.md
:focus-visible shows the ring for keyboard interaction and withholds it from a mouse click, which removes the reason people delete focus styles in the first place.
.button:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}At least 2px, and at least 3:1 against whatever sits behind it.
Full code: examples/focus.md
The contract a modal owes, whoever implements it: focus moves in on open, cannot leave while it is open, Escape closes it, and focus returns to the element that opened it.
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">{title}</h2>
…
</div>Focus returning to the trigger is the half most often missed — without it a keyboard user is dropped back at the top of the document with no idea where they were.
Full code: examples/focus.md
Content that arrives without a navigation is silent unless a live region announces it.
<div role="status">{savedMessage}</div>
<div role="alert">{errorMessage}</div>role="status" waits for a pause; role="alert" interrupts. The region must be in the DOM before the message arrives — inserting the region and its content together often announces nothing.
Colour is an accent on the signal, never the signal.
<span className={statusClass}>
<span aria-hidden="true">{status === "error" ? "×" : "✓"}</span>
<span>{status === "error" ? "Failed" : "Complete"}</span>
</span>The icon carries it visually and the text carries it to a screen reader; remove the colour and both still work.
Full code: examples/color.md
Animate by opting in, so a reader who has asked for less motion gets it by default.
@media (prefers-reduced-motion: no-preference) {
.card {
animation: slide-in 300ms ease-out;
}
}reduce means minimise rather than eliminate — a fade in place of a slide keeps the feedback and drops the vestibular trigger.
</patterns>
<red_flags>
Breaks at runtime:
outline: none with no replacement — keyboard users lose their position entirely; scope the ring to :focus-visible instead of removing it.<div> or <span> with an onClick — not focusable, not activated by Enter or Space, announced as nothing; use a <button>.role="button" on a <div> — supplies the word and none of the behaviour, so Tab, Enter and Space are all still owed.aria-hidden="true" on anything focusable — focus lands on an element the screen reader insists is not there.tabindex above zero — hoists the element ahead of the whole document order, and one such value disorders the entire page.Surprising behaviour:
alt="" is the correct value for a decorative image; omitting alt entirely makes a screen reader read the filename.aria-atomic="true" makes it re-read the whole region.role="alert" interrupts whatever is being read, so a per-keystroke validation message in one is unusable.<label> wrapping the input and a for/htmlFor both work; two labels on one input do not, and only one is announced.prefers-reduced-motion: reduce asks for less, not none — removing essential feedback overshoots.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.