| name | focus-states |
| description | Generates accessible focus indicators meeting WCAG 2.4.7 and 2.4.11 requirements. Use when styling :focus-visible, keyboard navigation indicators, or fixing focus ring visibility issues. |
Focus States Generator
Overview
Generate accessible, visible focus indicators for interactive elements. Creates focus styles that meet WCAG 2.4.7 (Focus Visible) and 2.4.11 (Focus Appearance) requirements while maintaining visual design consistency.
When to Use
- Setting up focus styles for a design system
- Ensuring keyboard navigation is visible
- Creating custom focus rings that match brand
- Fixing "invisible" focus states in existing UI
Quick Reference: WCAG Requirements
| Criterion | Requirement | Level |
|---|
| 2.4.7 Focus Visible | Focus indicator must be visible | AA |
| 2.4.11 Focus Appearance | 2px+ perimeter, 3:1 contrast | AAA |
| 2.4.12 Focus Not Obscured | Focus not hidden by other content | AA |
The Process
- Get brand colors: Primary color and background colors
- Ask style preference:
- Ring (outline around element)
- Glow (soft shadow-based)
- Underline (for text links)
- Hybrid (ring + offset)
- Ask visibility approach: Always visible or focus-visible only?
- Generate: Create focus tokens and utility classes
- Test guidance: Provide keyboard testing checklist
Focus Style Options
| Style | Character | Best For |
|---|
| Ring | Clean, defined | Buttons, inputs, cards |
| Glow | Soft, modern | Dark themes, premium UI |
| Underline | Minimal | Text links, nav items |
| Offset | High contrast | When ring blends with element |
| Inset | Subtle | Contained elements |
Output Formats
CSS Custom Properties + Utilities:
:root {
--focus-ring-color: #2563eb;
--focus-ring-width: 2px;
--focus-ring-offset: 2px;
--focus-ring-style: solid;
--focus-ring: var(--focus-ring-width) var(--focus-ring-style) var(--focus-ring-color);
--focus-ring-offset-shadow: 0 0 0 var(--focus-ring-offset) var(--color-background);
--focus-ring-shadow: 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
}
*:focus {
outline: none;
}
*:focus-visible {
outline: var(--focus-ring);
outline-offset: var(--focus-ring-offset);
}
.focus-ring:focus-visible {
outline: none;
box-shadow:
var(--focus-ring-offset-shadow),
var(--focus-ring-shadow);
}
Component-Specific Focus:
.btn:focus-visible {
outline: 2px solid var(--focus-ring-color);
outline-offset: 2px;
}
.input:focus-visible {
outline: none;
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.2);
}
.card:focus-visible {
outline: 2px solid var(--focus-ring-color);
outline-offset: 4px;
}
a:focus-visible {
outline: none;
text-decoration-thickness: 2px;
text-underline-offset: 4px;
background-color: rgb(37 99 235 / 0.1);
border-radius: 2px;
}
.skip-link:focus {
position: fixed;
top: 1rem;
left: 1rem;
z-index: 9999;
padding: 1rem;
background: var(--color-background);
outline: 2px solid var(--focus-ring-color);
}
Tailwind Config:
module.exports = {
theme: {
extend: {
ringColor: {
DEFAULT: '#2563eb',
},
ringWidth: {
DEFAULT: '2px',
},
ringOffsetWidth: {
DEFAULT: '2px',
},
}
},
plugins: [
plugin(function({ addVariant }) {
addVariant('focus-visible-within', '&:has(:focus-visible)')
})
]
}
Tailwind Utilities:
<button class="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2">
Button
</button>
<button class="focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-blue-500/30">
Glow Button
</button>
JSON Tokens:
{
"focus": {
"ring": {
"color": { "value": "{color.primary.500}" },
"width": { "value": "2px" },
"offset": { "value": "2px" },
"style": { "value": "solid" }
},
"glow": {
"color": { "value": "{color.primary.500}" },
"blur": { "value": "4px" },
"spread": { "value": "2px" },
"opacity": { "value": "0.3" }
}
}
}
Dark Mode Focus
:root {
--focus-ring-color: #2563eb;
--focus-ring-offset-color: #ffffff;
}
:root.dark {
--focus-ring-color: #60a5fa;
--focus-ring-offset-color: #1f2937;
}
Focus Indicator Contrast
WCAG 2.4.11 requires 3:1 contrast between:
- Focus indicator and adjacent background
- Focus indicator and the element itself
Safe combinations:
| Background | Focus Ring | Ratio |
|---|
| White | #2563eb (blue-600) | 4.7:1 ✓ |
| Gray-100 | #1d4ed8 (blue-700) | 6.5:1 ✓ |
| Gray-900 | #60a5fa (blue-400) | 6.2:1 ✓ |
| Black | #93c5fd (blue-300) | 9.4:1 ✓ |
Interactive States Order
Apply states in this specificity order:
.element { }
.element:hover { }
.element:focus { }
.element:focus-visible { }
.element:active { }
.element:disabled { }
Testing Checklist
Common Fixes
Focus disappears on click:
button:focus-visible { outline: ... }
Focus hidden behind sticky header:
:target { scroll-margin-top: 80px; }
*:focus { scroll-margin-top: 80px; }
Focus invisible on element with background:
outline-offset: 2px;