Interfaces that work with a keyboard, a screen reader and any language
jubayer-accessibility checks the things that stop people using a product: focus, keyboard support, names and roles, forms, motion and zoom, and the language and direction of each page.
- Free, MIT License
- 10 files, 26 KB zip
Updated 28 October 2026
When Claude uses it
Claude reads this description from the skill's SKILL.md and loads the skill when your request matches it.
Reviews and fixes web interface accessibility against WCAG 2.2 and the WAI-ARIA Authoring Practices, covering native semantics, visible focus, keyboard support, focus trapping, hit areas, form labels and errors, accessible names, live regions, alt text, reduced motion and zoom, plus right-to-left and bilingual pages (Arabic/English, Bangla/English) with lang and dir, translated accessible names, language switchers, RTL arrow keys and focus order, and mixed-direction text. Use when building or auditing any UI for keyboard or screen-reader users, adding a modal, form, menu, tabs or custom widget, checking a page against WCAG, or shipping a translated or RTL version of an interface.
What it covers
Native elements first
A button is a button. ARIA only where the platform has no element for the job.
Visible focus and full keyboard support
Every control reachable and visible with a keyboard, with focus trapped and restored in dialogs.
Names, labels and errors
Every control named and labelled, errors announced and placed next to the field.
Hit areas, motion and zoom
Targets large enough to hit, reduced motion respected, and pages that survive zoom and larger text.
Language of the page and its parts
lang and dir on the page and on passages in another language, so screen readers pick the right voice.
Translated accessible names
aria-label, alt text, live messages and the document title in the page's language.
Arrow keys and focus order in RTL
Arrows follow the visual direction, and focus order matches what people see, with no reorder hacks.
What this version adds
A reference on language and direction: WCAG 3.1.1 and 3.1.2, how screen readers choose a voice, language switchers named in their own script, arrow keys in RTL widgets, focus order versus visual order, and mixed-direction text and input.
Try it with a prompt like this
Check the Arabic version of my pricing page for accessibility. The tabs and the language switcher feel wrong with VoiceOver and the keyboard.
Or run it by name in Claude Code: type /jubayer-accessibility and what you want checked.
View SKILL.md
The skill's main file, as it is in the zip. The reference files it links to are listed below it.
jubayer-accessibility/SKILL.md · Show the full file
--- name: jubayer-accessibility description: Reviews and fixes web interface accessibility against WCAG 2.2 and the WAI-ARIA Authoring Practices, covering native semantics, visible focus, keyboard support, focus trapping, hit areas, form labels and errors, accessible names, live regions, alt text, reduced motion and zoom, plus right-to-left and bilingual pages (Arabic/English, Bangla/English) with lang and dir, translated accessible names, language switchers, RTL arrow keys and focus order, and mixed-direction text. Use when building or auditing any UI for keyboard or screen-reader users, adding a modal, form, menu, tabs or custom widget, checking a page against WCAG, or shipping a translated or RTL version of an interface. --- # Accessibility Start from the platform. A `<button>` already takes focus and answers Enter and Space, a bound `<label>` already names its input, and the browser already draws a focus ring. Most accessibility bugs come from replacing those defaults, not from missing extras. Make every fix in the styling system the project already uses, with the exact values given here, not a value that merely looks close. A review is two passes. First the keyboard alone: every flow must finish without a pointer. Then a screen reader: every control must announce its name, role and state, in the page's language. When in doubt, keep the native element instead of rebuilding it, and take ARIA away instead of adding more. Contrast measurement and color fixes belong to `jubayer-colors`. Text sizing and iOS input zoom belong to `jubayer-typography`. Spatial RTL layout and mirroring belong to `jubayer-layout`; this skill covers what RTL and bilingual pages mean for keyboard and assistive-technology users. ## Native elements first The first rule of ARIA: don't use ARIA when a native element exists. `<button>` for actions, `<a href>` for navigation, never `<div onClick>`. A real link must support Cmd/Ctrl/middle-click. No ARIA is better than bad ARIA. See [semantics-and-aria.md](semantics-and-aria.md) for landmarks, button-vs-link and disabled states. ## Visible focus rings Style `:focus-visible`, not bare `:focus`. Keyboard users get a ring and mouse users usually don't. Prefer the browser's unmodified indicator. A custom ring needs a project focus token or another explicit color. Verify the whole indicator against every adjacent color it crosses, `currentColor` included. Use at least a `2px` solid perimeter or an equivalent visible area. Never use `outline: none` without a verified replacement, and preserve system colors in forced-colors mode. Recipes are in [focus-and-keyboard.md](focus-and-keyboard.md). ## Full keyboard support Every pointer interaction needs a keyboard path. Follow the ARIA APG patterns: Escape closes overlays, arrow keys move within composite widgets, Tab moves between widgets, Enter and Space activate. Use only `tabindex="0"` to join the natural tab order and `tabindex="-1"` for programmatic focus. Positive values break that order. Composite widgets use roving tabindex, where the active item is `0` and every other is `-1`. ## Trap and restore focus Modals set `inert` on the background content, move focus inside on open and return focus to the trigger on close. Add `overscroll-behavior: contain` so background content doesn't scroll. ## Minimum hit area WCAG 2.5.8's Level AA baseline is a 24×24 CSS-pixel target, or one of its spacing, equivalent-control, inline, user-agent and essential exceptions. Aim for 44×44px on touch and 40×40px on desktop where density permits. Extend with a pseudo-element when the visible element should stay smaller. Never let extended hit areas overlap. Give decorative layers `pointer-events: none`, so a glow never swallows the clicks meant for the control beneath it. Sizes and collision rules are in [hit-areas.md](hit-areas.md). ## Label and type every control Every input gets a `<label for>` or a wrapping `<label>`. A placeholder is never a label. Label and control share one hit target, with no dead zone between a checkbox and its text. Add `autocomplete` with a meaningful `name`, plus the `type` and `inputmode` that summon the right keyboard. Never block paste; users paste passwords and one-time codes. See [forms.md](forms.md). ## Errors that announce Keep submit enabled until the request starts, then disable with a spinner and the original label. Validate on submit. Mark failing fields `aria-invalid="true"`, point `aria-describedby` at the inline error text and focus the first invalid field. Use native `disabled` when a control is genuinely unavailable. Reach for `aria-disabled="true"` only when it should stay focusable, then block pointer, keyboard and form behavior in code and style the state explicitly. ## Accessible names everywhere Icon-only buttons need a descriptive `aria-label`. Visible label text must appear in the accessible name. Decorative elements get `aria-hidden="true"`, never on a focusable element. ## Don't rely on color alone Status needs a redundant cue: an icon, text, or an underline alongside the color. Work out which WCAG contrast requirement applies, then use `jubayer-colors` to measure the rendered pair. When it fails, report the pair and the requirement it misses, and leave the colors alone unless asked. ## Honor prefers-reduced-motion Wrap motion in `@media (prefers-reduced-motion: no-preference)` so it is opt-in. Under reduced motion, replace slides and scales with opacity crossfades, and kill parallax and autoplay entirely. Two rules hold regardless of the preference. Autoplaying media needs a visible pause control, and toasts carrying an action or an error stay until dismissed. See [motion-and-zoom.md](motion-and-zoom.md). ## Announce dynamic content Three mechanisms, three jobs. `aria-describedby` carries field-specific validation. A polite live region (`role="status"`) carries non-urgent updates not tied to a control, such as toasts and result counts. `role="alert"` carries urgent untied errors and nothing else. Repeated polite announcements need a stable empty region rendered before its text updates. Dynamically inserted alerts vary in support, so test them on the screen readers you target. See [screen-readers.md](screen-readers.md). ## Alt text by purpose Decorative images get `alt=""`. Informative images describe the meaning. Functional images describe the action: a search icon button is `alt="Search"`, not `alt="magnifying glass"`. ## Structure is navigation Use headings that describe their sections and form a coherent outline. Give the page one `<h1>` and nest the levels below it without skipping. Expose one visible primary `<main>` landmark. When repeated navigation or chrome precedes it, make a "Skip to content" link the first focusable element. Anchored headings get `scroll-margin-top`. ## Survive zoom and text resize The page must work at 200% zoom and reflow at 320px width without horizontal scrolling. Use `min-height` rather than fixed `height` on text containers. Prefer `rem` breakpoints where they fit the codebase, and never let the viewport meta cap how far the reader can zoom. ## Language, direction and bilingual pages Set `lang` on `<html>` (WCAG 3.1.1) and `dir="rtl"` on right-to-left pages, as attributes, not only CSS `direction`. Mark passages in another language with `lang`, and `dir` when the direction differs (WCAG 3.1.2). Screen readers pick the voice from `lang`; without it, Arabic or Bangla may be read by an English voice. Translate every accessible name with the page: `aria-label`, alt text, live region messages, `title` and `document.title`. Name language switcher options in their own language with `lang` and `hreflang`, never with flags. In RTL, Left and Right arrows in horizontal widgets follow the visual direction, so ArrowLeft means next. Keep focus order matching visual order through `dir` and logical properties, never `row-reverse` or `order` hacks (WCAG 2.4.3, 1.3.2). Isolate inserted values with `<bdi>`, give user text and unknown-language inputs `dir="auto"`, and show validation messages in the page language. Rules, code and test steps are in [language-and-direction.md](language-and-direction.md). ## Before you finish | Mistake | Fix | | --- | --- | | Custom focus color assumed to work everywhere | Verify it against every adjacent color and in forced-colors mode | | Repeated polite update inconsistently announced | Keep a stable empty status region and update its text | | `assertive` live region for a routine toast | Use `polite`; reserve `assertive` for errors | | `aria-hidden="true"` on a focusable element | Remove it or make the element non-focusable | | Submit disabled until the form is valid | Keep it enabled; validate on submit and focus the first error | | Hover treatment stuck after a tap on touch | Gate hover styling with `@media (hover: hover)` | | Tooltip on a natively `disabled` control | Text beside it, or `aria-disabled` so it stays focusable | | No `lang` on `<html>`, or `lang="en"` on every locale | Render the locale's `lang` and `dir` per route | | RTL set only with CSS `direction: rtl` | Put `dir="rtl"` on `<html>` or the RTL container | | English `aria-label` or alt text on an Arabic or Bangla page | Take accessible strings from the same translation catalogue | | Language switcher with flags or names in the current language | Each name in its own language, with `lang` and `hreflang` | | `ArrowRight` hard-coded as next in a horizontal widget | Map arrows from the element's direction; swap in RTL | | RTL faked with `row-reverse` or `order` | `dir="rtl"` and logical properties, so DOM order matches visual order | | Native validation bubble in the browser's language | Translated inline errors or `setCustomValidity()` text | ## Reporting **Severity.** `HIGH` prevents a task, hides content from assistive technology, or creates a systemic failure. `MEDIUM` makes an interaction meaningfully harder. `LOW` is isolated polish. **Verification.** Without a browser: accessible names on every interactive element, keyboard handlers on non-native controls, focus styles, `prefers-reduced-motion` guards, form labels bound to their inputs, and on bilingual or RTL pages `lang` and `dir` on `<html>` and on other-language parts, translated accessible names and direction-aware arrow-key handlers. With one: tab the flow in order, read computed names and roles from the accessibility tree, confirm a visible focus indicator at every stop and run an automated audit. On RTL or bilingual pages, repeat the keyboard walk with `dir="rtl"` and listen with an Arabic or Bangla voice installed (see [language-and-direction.md](language-and-direction.md)). Report every check you could not run as `Not verified`. **Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in: | Severity | Location | Before | After | Why | | --- | --- | --- | --- | --- | `Location` is `path/to/file:line`. `Why` names the principle and the user impact. End with `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable accessibility findings" and report verification. ## Origin Based on Jakub Krehel's `better-accessibility` skill (MIT License), renamed and edited by Maniruzzaman Jubayer, with added right-to-left and bilingual rules. See LICENSE.
Files in the zip
- jubayer-accessibility/LICENSE1 KB
- jubayer-accessibility/SKILL.md11 KB
- jubayer-accessibility/agents/openai.yaml1 KB
- jubayer-accessibility/focus-and-keyboard.md7 KB
- jubayer-accessibility/forms.md4 KB
- jubayer-accessibility/hit-areas.md4 KB
- jubayer-accessibility/language-and-direction.md12 KB
- jubayer-accessibility/motion-and-zoom.md4 KB
- jubayer-accessibility/screen-readers.md5 KB
- jubayer-accessibility/semantics-and-aria.md5 KB
Based on Jakub Krehel's better-* skills, MIT License. The LICENSE in the zip keeps his copyright and mine.
Install jubayer-accessibility in 3 steps
Pick where you use Claude. The same zip works in both places.
In the Claude app or on Claude.ai
- Step 1:
Open Settings › Capabilities. Under Code execution and file creation, switch on Run code in the cloud. Skills need it.

Settings › Capabilities: Run code in the cloud, on - Step 2:
Open Settings › Customize › Skills, choose + Add, then Upload skill.

Customize › Skills › + Add › Upload skill - Step 3:
Drop the skill's zip, as it downloads from here, on Upload a skill and choose Upload. Claude scans it, then lists it under Skills, where you switch it on or off.

Upload a skill: drop the .zip, then Upload
In Claude Code
- Step 1:
Unzip the skill into your skills folder:
~/.claude/skills/for every project, or.claude/skills/in one project.
Unzip into ~/.claude/skills - Step 2:
Check the folder is there. Each skill is a folder with a SKILL.md inside.

Each skill is its own folder - Step 3:
Ask Claude for the job in plain words, or type the skill's name as a command, such as
/jubayer-ui.
Run it by name or just ask
Building a product people of every ability can use?
I design products with accessibility in the first flow, not in a fix list after launch, in English and Arabic.
Questions people ask
- It checks the rules it covers and reports what it could not verify. Compliance still needs testing with real assistive technology and, ideally, people who use it.
- Screen readers use lang to choose a voice and pronunciation. Without it, Arabic text can be read by an English voice. dir keeps reading and focus order right.
- No. A flag is a country, not a language. Name each language in itself, such as English, العربية or বাংলা, with lang on each option.
