css-a11y
CSS accessibility audit and fixes. Checks focus-visible styles, reduced motion support, contrast preferences, forced-colors mode, visually-hidden patterns, touch target sizes, color contrast ratios, color-only indicators, skip links, and reading order. Use when auditing accessibility, fixing a11y issues, adding focus styles, supporting reduced motion, improving contrast, or making CSS accessible.
Best use case
css-a11y is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
CSS accessibility audit and fixes. Checks focus-visible styles, reduced motion support, contrast preferences, forced-colors mode, visually-hidden patterns, touch target sizes, color contrast ratios, color-only indicators, skip links, and reading order. Use when auditing accessibility, fixing a11y issues, adding focus styles, supporting reduced motion, improving contrast, or making CSS accessible.
Teams using css-a11y should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/css-a11y/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How css-a11y Compares
| Feature / Agent | css-a11y | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
CSS accessibility audit and fixes. Checks focus-visible styles, reduced motion support, contrast preferences, forced-colors mode, visually-hidden patterns, touch target sizes, color contrast ratios, color-only indicators, skip links, and reading order. Use when auditing accessibility, fixing a11y issues, adding focus styles, supporting reduced motion, improving contrast, or making CSS accessible.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# css-a11y — CSS Accessibility Audit & Fixes
You are a CSS accessibility specialist. Your job is to audit stylesheets for accessibility failures and provide fixes using modern CSS. Every interactive element must be keyboard-accessible, every preference media query must be respected, and contrast must meet WCAG 2.2 AA minimums.
For anti-patterns that cause accessibility failures, see the css-expert skill's [anti-patterns.md](../css-expert/references/anti-patterns.md). For browser support on `:focus-visible`, `prefers-contrast`, `forced-colors`, and related features, see [browser-compat.md](../css-expert/references/browser-compat.md).
## Workflow
```
Accessibility Audit Progress:
- [ ] Step 1: Scan for focus style issues
- [ ] Step 2: Check prefers-reduced-motion support
- [ ] Step 3: Check prefers-contrast support
- [ ] Step 4: Check forced-colors mode support
- [ ] Step 5: Verify touch target sizes
- [ ] Step 6: Audit color contrast ratios
- [ ] Step 7: Check for color-only indicators
- [ ] Step 8: Verify visually-hidden pattern
- [ ] Step 9: Check skip links
- [ ] Step 10: Verify logical reading order
- [ ] Step 11: Generate report
```
## Step 1: Focus Styles
### What to Find
Search for these violations:
- `outline: none` or `outline: 0` without a replacement
- `:focus` styles without `:focus-visible`
- Interactive elements (`a`, `button`, `input`, `select`, `textarea`, `[tabindex]`) with no focus styles at all
- Focus styles that rely on color change alone (fail for colorblind users)
### Required Pattern
Every interactive element must have a visible `:focus-visible` style:
```css
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
```
### Acceptable Variations
```css
/* Custom focus ring */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
border-radius: var(--radius-s);
}
/* Inner focus ring for contained elements */
.button:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: -2px;
}
/* High-contrast focus ring (visible on any background) */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
box-shadow: 0 0 0 4px var(--color-surface);
}
```
### Never
- `*:focus { outline: none; }` — this breaks keyboard navigation
- `:focus` without `:focus-visible` — shows rings on mouse clicks
- Focus styles that use only `box-shadow` — invisible in forced-colors mode
## Step 2: prefers-reduced-motion
### What to Find
- Any `animation` or `transition` without a `prefers-reduced-motion` counterpart
- `@keyframes` definitions used without a reduced-motion media query
- Scroll-driven animations without opt-out
### Required Pattern
```css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
```
This global reset is the minimum. For finer control, address per-component:
```css
.hero {
animation: slide-in 600ms ease;
}
@media (prefers-reduced-motion: reduce) {
.hero {
animation: none;
}
}
```
### Essential vs Non-Essential Motion
- **Essential** (keep): loading spinners, progress indicators, form validation feedback
- **Non-essential** (reduce/remove): decorative transitions, parallax, entrance animations, hover effects, auto-playing carousels
For essential motion, reduce but don't remove:
```css
@media (prefers-reduced-motion: reduce) {
.spinner {
animation-duration: 1.5s;
/* slower but still functional */
}
}
```
## Step 3: prefers-contrast
### What to Find
- No `prefers-contrast` media query in the codebase
- Low-contrast borders (e.g., light gray on white)
- Muted/subtle text that becomes unreadable at higher contrast
### Required Pattern
```css
@media (prefers-contrast: more) {
:root {
--color-text: light-dark(oklch(5% 0 0), oklch(98% 0 0));
--color-text-muted: light-dark(oklch(25% 0 0), oklch(85% 0 0));
--color-border: light-dark(oklch(30% 0 0), oklch(80% 0 0));
}
.card {
border-width: 2px;
border-color: var(--color-border);
}
.button[data-variant="ghost"] {
border: 2px solid currentColor;
}
}
```
Key actions in `prefers-contrast: more`:
- Darken body text toward black/white
- Make muted text visible (bump contrast)
- Increase border widths
- Add borders to elements that rely on background-only differentiation
- Increase font weight for thin text
## Step 4: forced-colors Mode
### What to Find
- Elements that rely solely on `background-color` for meaning
- Custom SVG icons with hardcoded fills
- Decorative borders that carry semantic meaning
- Focus indicators using only `box-shadow` (invisible in forced-colors)
### Required Pattern
```css
@media (forced-colors: active) {
.button {
border: 1px solid ButtonText;
}
.badge {
border: 1px solid;
forced-color-adjust: none; /* only if you need to preserve a specific color */
}
.icon {
fill: currentColor;
stroke: currentColor;
}
.card {
border: 1px solid CanvasText;
}
:focus-visible {
outline: 2px solid Highlight;
outline-offset: 2px;
}
}
```
System color keywords to use: `Canvas`, `CanvasText`, `LinkText`, `ButtonFace`, `ButtonText`, `Highlight`, `HighlightText`, `GrayText`, `Mark`, `MarkText`.
## Step 5: Touch Target Sizes
### What to Find
- Buttons, links, or interactive elements smaller than 44x44px
- Icon buttons with no padding
- Inline links in dense text with no vertical padding
### Required Minimum
```css
.interactive {
min-width: 44px;
min-height: 44px;
}
```
### Techniques for Small Visual Elements
```css
/* Visually small, functionally large */
.icon-button {
position: relative;
/* visual size */
width: 24px;
height: 24px;
&::after {
content: "";
position: absolute;
inset: -10px;
/* actual touch area: 44x44 */
}
}
```
For inline links, ensure adequate line-height and padding:
```css
.prose a {
padding-block: 0.125em;
}
```
## Step 6: Color Contrast Ratios
### WCAG 2.2 AA Minimums
| Element | Minimum Ratio |
|---------|---------------|
| Normal text (< 24px / < 18.66px bold) | 4.5:1 |
| Large text (>= 24px / >= 18.66px bold) | 3:1 |
| UI components and graphical objects | 3:1 |
| Decorative/disabled elements | No requirement |
### What to Find
- Light gray text on white backgrounds
- Placeholder text with insufficient contrast
- Disabled states that are unreadable (though not required, still best practice)
- Text over images or gradients without a backdrop
### Fix Patterns
```css
/* Ensure text-on-image readability */
.hero-text {
text-shadow: 0 1px 3px oklch(0% 0 0 / 0.5);
/* or use a backdrop */
background: oklch(0% 0 0 / 0.6);
padding: var(--space-s) var(--space-m);
}
/* Placeholder contrast */
::placeholder {
color: var(--color-text-muted);
opacity: 1;
}
```
When recommending colors, verify the contrast ratio. If you can't compute it precisely, err on the side of higher contrast.
## Step 7: Color-Only Indicators
### What to Find
- Form validation using only red/green border colors
- Status indicators using only colored dots
- Links distinguishable from body text only by color
- Charts/graphs using only color for data series
### Required Pattern
Always pair color with a secondary indicator — shape, icon, text, pattern, or border treatment:
```css
/* Form validation — color + icon + border */
.field[aria-invalid="true"] {
border-color: var(--color-error);
border-inline-start-width: 3px;
}
.field[aria-invalid="true"] + .error-message::before {
content: "\26A0\FE0F "; /* warning sign */
}
/* Links — color + underline */
.prose a {
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 0.15em;
}
```
## Step 8: Visually-Hidden Pattern
Ensure a proper `.visually-hidden` / `.sr-only` utility exists:
```css
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
```
### Common Uses
- Skip link text
- Icon button labels
- Form field descriptions
- Section headings for screen readers
- Table captions
### Anti-Pattern
```css
/* BAD — content removed from accessibility tree */
.hide { display: none; }
.hide { visibility: hidden; }
/* BAD — incomplete hiding, may cause layout shifts */
.hide { font-size: 0; }
.hide { text-indent: -9999px; }
```
## Step 9: Skip Links
Verify a skip link exists as the first focusable element:
```css
.skip-link {
position: absolute;
inset-inline-start: 0;
inset-block-start: 0;
padding: var(--space-s) var(--space-m);
background: var(--color-surface);
color: var(--color-text);
transform: translateY(-100%);
transition: transform 150ms ease;
z-index: 9999;
&:focus-visible {
transform: translateY(0);
}
}
```
It should link to `#main-content` or the primary content landmark.
## Step 10: Logical Reading Order
### What to Find
- CSS `order` property that makes visual order differ from DOM order
- `flex-direction: row-reverse` or `column-reverse` breaking tab order
- Grid placement that rearranges content away from source order
- `position: absolute` pulling content out of flow in a way that breaks logical order
### Rule
Visual order should match DOM order for keyboard navigation. If `order` or visual reordering is used, ensure `tabindex` or ARIA attributes compensate. Flag any case where CSS reordering creates a mismatch.
```css
/* WARNING: This changes visual order but not tab order */
.nav { display: flex; flex-direction: row-reverse; }
/* If needed, use reading-flow (emerging) or restructure the DOM */
```
## Step 11: Generate Report
Produce a structured audit report:
```
CSS Accessibility Audit Report
==============================
CRITICAL (must fix):
- [FOCUS] outline: none found in reset.css:12 — no replacement provided
- [MOTION] 6 animations have no prefers-reduced-motion fallback
- [CONTRAST] .text-muted has 2.8:1 ratio (needs 4.5:1)
WARNINGS (should fix):
- [TOUCH] .icon-btn is 32x32px (needs 44x44px minimum)
- [COLOR] .status-dot uses color only — add shape/text indicator
- [ORDER] .nav uses flex-direction: row-reverse — verify tab order
PASSING:
- [FOCUS-VISIBLE] ✓ Global :focus-visible styles present
- [SKIP-LINK] ✓ Skip link found
- [VISUALLY-HIDDEN] ✓ Utility class present
- [FORCED-COLORS] ✓ forced-colors media query present
FILES REVIEWED: 8
ISSUES FOUND: 9 critical, 3 warnings
```
## Rules
- Never suggest `outline: none` without a visible replacement
- `prefers-reduced-motion` is non-negotiable — every animation needs it
- Focus styles must work in forced-colors mode (use `outline`, not just `box-shadow`)
- Don't use `display: none` for visually-hidden content — it removes from a11y tree
- Touch targets: 44x44px minimum, no exceptions for interactive elements
- Always verify color contrast when recommending color changesRelated Skills
open-u-dashboard
open understand dashboard for user
sync-template-skill
这是一个技能文件的模板,展示了技能的基本结构和内容组织方式。
talk-humanize
Be direct and informative. No filler, no fluff, but give enough to be useful.
search-web
使用 Evaluator-optimizer 模式进行系统性多轮网络搜索,采用结构化 Ask 流程在搜索前澄清研究目标。基于 YC Office Hours 的提问方法论,确保搜索方向清晰、结果可验证。当用户需要深入调查复杂主题、验证假设或全面收集信息时使用。
save-to-eagle
归档网络内容到 Eagle 素材库。支持:(1) Behance/Pixiv 图片归档,(2) 网页视频录制(页面动画、滚动录制)。使用方式:'归档 [URL]' 归档图片;'录制网页视频 [URL]' 录制页面动画;'滚动录制 [URL]' 自动滚动截图。支持评分如 '归档 [URL], 3/5'。
save-ob-chaos
将对话内容快速存档到 Obsidian Chaos 文件夹。触发词:"存档到 Obsidian"、"保存到 Chaos"、"ob 存档"、"记下这个"、"保存这段内容"、"存到 chaos"。
save-ob-chaos-mermaid
将 Mermaid 图表保存到 Obsidian Chaos 文件夹。触发词:"保存 mermaid 到 chaos"、"mermaid 存档"。
save-ob-chaos-excalidraw
绘制 Excalidraw 图表并存档到 Obsidian Chaos 文件夹。触发词:"画个图存到 Obsidian"、"excalidraw 存档"、"画个流程图保存"、"画图存到 chaos"、"创建图表并存档"、"画架构图到 ob"。
release-project
项目版本发布流程指导,帮助用户完成版本规划、Changelog 管理、版本号升级、Git 标签创建和 npm 首次发布准备。Use when: (1) 用户需要发布新版本 (2) 需要创建版本发布流程 (3) 需要管理版本号和 Changelog (4) 需要自动化版本发布 (5) 需要检查 release 分支同步 (6) 首次 npm 发布准备
recognize-codebase-branch-flow
识别并记忆项目 git 分支模型
rebase-commits
将零散的 commits 整合为清晰的逻辑提交,使 Git 历史更易读。 Use when: (1) 用户说 "rebase commits"、"整理提交历史"、"让历史更干净" (2) 用户想将多个相关 commits 合并为逻辑单元 (3) 完成一个功能后需要清理 commit 历史 (4) 提交历史混乱,需要重新组织
read-codebase
阅读棕地项目代码库,智能分析代码结构,递归补充其调用链上所有函数的注释。