accessibility-checks
Use when verifying accessibility (WCAG 2.2 AA) of a rendered UI in a real browser before reporting done — the omp-native replacement for the chrome-devtools…
Skill by jhlee0409
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/jhlee0409-accessibility-checks
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documents, design, devops, agent-building
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/accessibility-checks
What's inside
- Skill name
accessibility-checks- Description
- Use when verifying accessibility (WCAG 2.2 AA) of a rendered UI in a real browser before reporting done — the omp-native replacement for the chrome-devtools a11y plugin. Runs a concrete ordered checklist with omp's builtin `browser` tool (open tab → `tab.evaluate` DOM query / `elementFromPoint` / `getComputedStyle` / `getBoundingClientRect` / `tab.screenshot`), NOT chrome-devtools MCP or axe-in-CI self-report. Every criterion gets PASS/FAIL WITH the measured value (contrast ratio, px size, tabindex order); computed-style/rect is the evidence, source-reading alone is INVALID. Load when auditing a11y, before claiming a UI is accessible. Triggers on accessibility, a11y, WCAG, keyboard navigation, contrast, screen reader, focus, tap target.
- License
- MIT
- Instructions
- 125 lines in SKILL.md · view on GitHub
Files (1)
- SKILL.md
SKILL.md (first lines)
# Accessibility checks — WCAG 2.2 AA, measured in a real browser
Accessibility is a MEASURED property of the rendered DOM, never a source-reading guess. `aria-label` in JSX proves nothing if the runtime node is `display:none`, the contrast ratio is 3.9:1, or the tap target renders at 32px. This skill runs the audit against the LIVE page with omp's native `browser` tool and cites the number for every verdict. "looks accessible" is BANNED — every row carries a measured value or it is CANT-VERIFY.
## Setup
1. `git diff --name-only` → identify the changed route/component. Read its `DESIGN.md` (tokens/tone) if present.
2. Start the dev server on its own port; wait for a stable anchor before probing (a cold compile is not "down").
3. `browser` action `open` the changed route. Then drive every check with action `run` + `tab.evaluate`.
4. All snippets below run inside `tab.evaluate(() => { ... })` and RETURN a JSON value — that returned value is the evidence you cite.
## 1. Semantic structure — landmarks + heading order (WCAG 1.3.1, 2.4.6)
```js
tab.evaluate(() => {
const landmarks = [...document.querySelectorAll('header,nav,main,aside,footer,[role="banner"],[role="navigation"],[role="main"],[role="contentinfo"]')].map(e => e.tagName+ (e.getAttribute('role')?`[${e.getAttribute('role')}]`:''));
const headings = [...document.querySelectorAll('h1,h2,h3,h4,h5,h6')].map(h => ({ lvl:+h.tagName[1], text:h.textContent.trim().slice(0,40) }));
return { mainCount: document.querySelectorAll('main,[role=main]').length, landmarks, headings };
})
```
FAIL if: `mainCount !== 1`; no h1; heading levels skip (h2→h4). Cite the actual level sequence.
## 2. Keyboard nav + focus order + visible focus ring (WCAG 2.1.1, 2.4.3, 2.4.7)
- Enumerate the tab order and confirm it follows DOM/visual order (no positive `tabindex` reordering surprises):
```js
tab.evaluate(() => [...document.querySelectorAll('a[href],button,input,select,textarea,[tabindex]')]
.filter(e => !e.disabled && e.offsetParent !== null)
.map(e => ({ tag:e.tagName, ti:e.tabIndex, label:(e.textContent||e.value||e.ariaLabel||'').trim().slice(0,30) })))
```
FAIL if any positive `tabIndex > 0` (manual reorder trap), or a control is reachable by mouse but absent from this list.
- Visible focus ring — focus each interactive node and read `:focus-visible` computed style; a ring is only real if `outline-width`/`box-shadow` actually changes on focus:
```js
tab.evaluate(() => {
const el = document.querySelector('button'); // repeat per control class
el.focus();
const s = getComputedStyle(el);
return { outlineWidth:s.outlineWidth, outlineStyle:s.outlineStyle, boxShadow:s.boxShadow };
})
```
FAIL if focused control shows `outline-style:none` AND no distinguishing `box-shadow` (invisible focus). `outline:0` with no replacement = FAIL.
## 3. Color contrast — compute the REAL ratio (WCAG 1.4.3, 1.4.11)
NEVER eyeball. Read `color` + effective background from `getComputedStyle`, compute the WCAG ratio, cite the number.
```js
tab.evaluate(() => {
const lum = ([r,g,b]) => { const f=v=>{v/=255; return v<=0.03928?v/12.92:((v+0.055)/1.055)**2.4;}; return 0.2126*f(r)+0.7152*f(g)+0.0722*f(b); };
const rgb = s => s.match(/\d+(\.\d+)?/g).map(Number).slice(0,3);
const bgOf = el => { for(let n=el; n; n=n.parentElement){ const b=getComputedStyle(n).backgroundColor; if(b && !/rgba?\(0, 0, 0, 0\)|transparent/.test(b)) return b; } return 'rgb(255,255,255)'; };
const ratio = el => { const s=getComputedStyle(el); const L1=lum(rgb(s.color)), L2=lum(rgb(bgOf(el))); return ((Math.max(L1,L2)+0.05)/(Math.min(L1,L2)+0.05)); };
return [...document.querySelectorAll('p,span,a,button,label,h1,h2,h3,li')]
.filter(e => e.textContent.trim() && e.offsetParent)
.map(e => { const s=getComputedStyle(e); const px=parseFloat(s.fontSize); const large = px>=24 || (px>=18.66 && +s.fontWeight>=700);
return { text:e.textContent.trim().slice(0,25), ratio:+ratio(e).toFixed(2), px, large, need: large?3:4.5 }; })
.filter(r => r.ratio < r.need);
})
```
Threshold: 4.5:1 normal text, 3:1 large text (≥24px, or ≥18.66px bold) and UI components/graphics. Any returned row = FAIL, cite `ratio` vs `need`. (This is opacity-blind — for `rgba`/overlay text also `tab.screenshot` and note it as a caveat.)
## 4. Tap-target size ≥44px (WCAG 2.5.8 AA)
```js
tab.evaluate(() => [...document.querySelectorAll('a[href],button,input,select,[role=button],[onclick]')]
.filter(e => e.offsetParent !== null)
.map(e => { const r=e.getBoundingClientRect(); return { label:(e.textContent||e.ariaLabel||'').trim().slice(0,25), w:Math.round(r.width), h:Math.round(r.height) }; })
.filter(r => r.w < 44 || r.h < 44))
```
FAIL for each returned control (cite w×h) unless it qualifies for the WCAG spacing/inline exception. Verify with `elementFromPoint(cx, cy)` that the visible hit area actually resolves to that control, not an overlay.
## 5. ARIA roles/states/names + form labels + errors (WCAG 1.3.1, 3.3.1, 3.3.2, 4.1.2)
```js
tab.evaluate(() => {
const nameOf = e => e.getAttribute('aria-label') || (e.getAttribute('aria-labelledby') && document.getElementById(e.getAttribute('aria-labelledby'))?.textContent?.trim()) || (e.labels && e.labels[0]?.textContent?.trim()) || e.textContent.trim();
const iconBtns = [...document.querySelectorAll('button,[role=button]')].filter(b => !nameOf(b)).map(b => b.outerHTML.slice(0,60));
const inputs = [...document.querySelectorAll('input,select,textarea')].filter(i => i.type!=='hidden').map(i => ({ type:i.type, hasLabel:!!nameOf(i), required:i.required, invalid:i.getAttribute('aria-invalid'), describedby:i.getAttribute('aria-describedby') }));
return { unnamedControls: iconBtns, inputsMissingLabel: inputs.filter(i=>!i.hasLabel) };
})
```
FAIL if: any interactive control has no accessible name (icon-only button); any form field lacks a programmatic label; an error state sets no `aria-invalid`/`aria-describedby` pointing at the message. Also sanity-check `tab.ariaSnapshot()` — roles must match intent (a `div onclick` with no role = FAIL). Trigger a validation error and re-run to confirm the error is announced.
## 6. Images alt + reduced-motion + reflow at 320px (WCAG 1.1.1, 2.3.3/1.4.4, 1.4.10)
```js
tab.evaluate(() => ({
imgsNoAlt: [...document.images].filter(i => !i.hasAttribute('alt')).map(i => i.src.slice(-40)),
decorativeOk: [...document.images].filter(i => i.alt==='' ).length,
reducedMotionHonored: matchMedia('(prefers-reduced-motion: reduce)').matches
}))
```
- Alt: FAIL for any `<img>` missing an `alt` attribute (decorative = `alt=""` is correct, informative empty = FAIL).
- Reduced motion: set the browser open with reduced-motion emulation (or check the CSS has a `@media (prefers-reduced-motion: reduce)` block that stops animation) — infinite auto-animation with no reduce guard = FAIL.
- Reflow: re-`open`/resize the tab to 320px width, then `tab.evaluate(() => ({ hScroll: document.documentElement.scrollWidth > window.innerWidth + 1, overflow: [...document.querySelectorAll('*')].filter(e=>e.scrollWidth>window.innerWidth+1).length }))`. Horizontal scroll at 320px = FAIL. Screenshot at 320px as artifact.
## 7. Interactive-state sweep — audit the OPEN state too
A closed-state-only audit is INVALID — a common failure mode is checking an accordion/menu/modal/dropdown only while CLOSED and calling it done. For each stateful component, drive it through **closed → open → hover → disabled** and re-run checks 2/3/4/5 in EACH state:
- Open the menu/modal (`tab.click`), then re-check contrast, tap-target, focus trap (focus must move into and stay within an open modal), and `aria-expanded`/`aria-modal`.
- `elementFromPoint()` on all 4 corners of the opened surface to confirm it is actually visible and not clipped by an `overflow:hidden` parent (rect-normal ≠ visible).
- Disabled controls: confirm `aria-disabled`/`disabled` present AND not focus-reachable.
Contrast/size measured only in the closed state does NOT cover the open state — state it explicitly.
## Verdict rules — pick exactly 1 of 3 per criterion (all first-class)
- **PASS** — measured value meets the WCAG threshold. Cite the number (ratio 5.8:1, 48×48px, tabindex 0…n in order).
- **FAIL** — measured value violates the threshold. Cite the number + the offending selector/text.
- **CANT-VERIFY** — could not measure (control not rendered, state not reachable, opacity/overlay defeats the compute). NOT "PASS with caveats".
## Output (BLUF first)
- **Conclusion**: overall PASS / FAIL / CANT-VERIFY (FAIL if any criterion failed).
- **Check results**: table — criterion (1–7), PASS/FAIL/CANT-VERIFY, **measured value** (ratio, px, order, name), evidence (the `tab.evaluate` JSON return or screenshot path). A row with no measured value is CANT-VERIFY, never PASS.
- **Defects**: per failure — selector + measured value vs required threshold + which WCAG SC + a screenshot of the state.
- Attach screenshots of key states (default + open state + 320px reflow).
## Constraints
- **Source-reading alone is INVALID.** `aria-label` in JSX / a Tailwind class in the file is NOT evidence. The evidence is the runtime `getComputedStyle` / `getBoundingClientRect` / `elementFromPoint` return from the live DOM.From jhlee0409/omni-harness-kit, MIT.
More from jhlee0409/omni-harness-kit
- 0.507score
localize
jhlee0409
Resolve a change request into an EXACT edit target before touching code, then gate the edit on tests — the localize → edit → validate loop.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
perf-checks
jhlee0409
Use when investigating or optimizing performance in a real browser/build/DB before reporting a fix — the omp-native replacement for the chrome-devtools…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
tdd
jhlee0409
Drive a single behavior change with Kent Beck's red → green → refactor loop — write the failing test FIRST, confirm it fails for the right reason, implement…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
chrome-verify-checks
jhlee0409
Use when verifying a chrome-extension/ change before reporting done.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
ui-verify-checks
jhlee0409
Use when verifying a frontend UI change in a real browser before reporting done.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
db-verify-checks
jhlee0409
Use when verifying data-shape claims against a real store — MongoDB or a SQL store (SQLAlchemy / SQLite / Postgres) — before reporting any data-touching…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
assess
jhlee0409
Read-only maintainability audit of a whole codebase — ranks the files most likely to cause maintenance pain (size × churn hotspots, test gaps, lint debt) and…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
blast-radius
jhlee0409
Enumerate the full impact set of a symbol before or during a change — every reference, caller, implementation/subtype, and reverse import — plus an explicit…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
llm-eng-checks
jhlee0409
Use when building or hardening an LLM feature — prompt design, RAG, eval harness, model/provider selection, guardrails, and token/cost budgeting — with…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
diagnose
jhlee0409
Disciplined diagnosis loop for a hard bug or performance regression — reproduce → minimize → hypothesize → instrument → fix the cause → regression-test.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
handoff
jhlee0409
Write the live working-set state into a resume block so a FRESH session can continue this work without re-narration.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
new-spec
jhlee0409
Scaffold a spec triplet (spec.md / plan.md / context.md) under specs/YYYYMMDD-<name>/ before starting a non-trivial piece of work.
SkillCFreshness: C★ 2Claude Code +9
Signals
- Stars
- 2
- Forks
- 1
- Open issues
- 0
- Last commit (this folder)
- Jul 14, 2026 (3 months ago)
- Last release
- Jul 22, 2026 (3 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/jhlee0409/omni-harness-kit
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from jhlee0409
- 0.507score
db-verify-checks
jhlee0409
Use when verifying data-shape claims against a real store — MongoDB or a SQL store (SQLAlchemy / SQLite / Postgres) — before reporting any data-touching…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
chrome-verify-checks
jhlee0409
Use when verifying a chrome-extension/ change before reporting done.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
perf-checks
jhlee0409
Use when investigating or optimizing performance in a real browser/build/DB before reporting a fix — the omp-native replacement for the chrome-devtools…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
ui-verify-checks
jhlee0409
Use when verifying a frontend UI change in a real browser before reporting done.
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
llm-eng-checks
jhlee0409
Use when building or hardening an LLM feature — prompt design, RAG, eval harness, model/provider selection, guardrails, and token/cost budgeting — with…
SkillCFreshness: C★ 2Claude Code +9 - 0.507score
assess
jhlee0409
Read-only maintainability audit of a whole codebase — ranks the files most likely to cause maintenance pain (size × churn hotspots, test gaps, lint debt) and…
SkillCFreshness: C★ 2Claude Code +9
Cite this page
accessibility-checks skill. Agent Mart. https://agentmart.dev/skills/jhlee0409-accessibility-checks (last updated 2026-10-24)