Proper focus styling is essential for accessibility. Here's how to implement it effectively.
Understanding Focus States#
1/* :focus - triggers on any focus */
2button:focus {
3 outline: 2px solid blue;
4}
5
6/* :focus-visible - triggers only on keyboard focus */
7button:focus-visible {
8 outline: 2px solid blue;
9}
10
11/* :focus-within - parent has focused child */
12.form-group:focus-within {
13 border-color: blue;
14}
15
16/* Difference in behavior */
17/* Mouse click: :focus activates, :focus-visible does not */
18/* Tab key: Both :focus and :focus-visible activate */Modern Focus Strategy#
1/* Remove default outline for mouse users */
2button:focus:not(:focus-visible) {
3 outline: none;
4}
5
6/* Show outline for keyboard users */
7button:focus-visible {
8 outline: 2px solid #4f46e5;
9 outline-offset: 2px;
10}
11
12/* Better approach: style :focus-visible directly */
13button {
14 outline: none; /* Be careful with this */
15}
16
17button:focus-visible {
18 outline: 2px solid #4f46e5;
19 outline-offset: 2px;
20}
21
22/* Alternative with box-shadow */
23button:focus-visible {
24 outline: none;
25 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.5);
26}Custom Focus Indicators#
1/* Ring-style focus */
2.btn:focus-visible {
3 outline: 2px solid transparent;
4 box-shadow:
5 0 0 0 2px white,
6 0 0 0 4px #4f46e5;
7}
8
9/* Inset focus */
10.card:focus-visible {
11 outline: none;
12 box-shadow: inset 0 0 0 3px #4f46e5;
13}
14
15/* Animated focus */
16.interactive:focus-visible {
17 outline: none;
18 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0);
19 animation: focus-ring 0.3s ease forwards;
20}
21
22@keyframes focus-ring {
23 to {
24 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.5);
25 }
26}
27
28/* Underline focus for links */
29a:focus-visible {
30 outline: none;
31 text-decoration: underline;
32 text-decoration-thickness: 2px;
33 text-underline-offset: 4px;
34}Form Elements#
1/* Input focus */
2input:focus-visible,
3textarea:focus-visible,
4select:focus-visible {
5 outline: none;
6 border-color: #4f46e5;
7 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2);
8}
9
10/* Checkbox and radio */
11input[type="checkbox"]:focus-visible,
12input[type="radio"]:focus-visible {
13 outline: 2px solid #4f46e5;
14 outline-offset: 2px;
15}
16
17/* Custom checkbox */
18.custom-checkbox:focus-visible + label::before {
19 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3);
20}
21
22/* Form group focus */
23.form-group:focus-within {
24 border-color: #4f46e5;
25}
26
27.form-group:focus-within label {
28 color: #4f46e5;
29}Button Styles#
1/* Primary button */
2.btn-primary {
3 background: #4f46e5;
4 color: white;
5 border: none;
6 padding: 0.75rem 1.5rem;
7 border-radius: 0.5rem;
8}
9
10.btn-primary:focus-visible {
11 outline: none;
12 box-shadow:
13 0 0 0 2px white,
14 0 0 0 4px #4f46e5;
15}
16
17/* Secondary button */
18.btn-secondary {
19 background: white;
20 color: #4f46e5;
21 border: 2px solid #4f46e5;
22}
23
24.btn-secondary:focus-visible {
25 outline: none;
26 background: #eef2ff;
27 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3);
28}
29
30/* Icon button */
31.btn-icon {
32 width: 2.5rem;
33 height: 2.5rem;
34 border-radius: 50%;
35 display: flex;
36 align-items: center;
37 justify-content: center;
38}
39
40.btn-icon:focus-visible {
41 outline: none;
42 box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3);
43}Link Styles#
1/* Standard link */
2a:focus-visible {
3 outline: 2px solid currentColor;
4 outline-offset: 2px;
5 border-radius: 2px;
6}
7
8/* Navigation link */
9.nav-link:focus-visible {
10 outline: none;
11 background-color: rgba(79, 70, 229, 0.1);
12 box-shadow: inset 0 -2px 0 #4f46e5;
13}
14
15/* Card link (entire card clickable) */
16.card-link {
17 text-decoration: none;
18 display: block;
19}
20
21.card-link:focus-visible {
22 outline: none;
23}
24
25.card-link:focus-visible .card {
26 box-shadow:
27 0 0 0 2px white,
28 0 0 0 4px #4f46e5,
29 0 4px 6px rgba(0, 0, 0, 0.1);
30}
31
32/* Skip link */
33.skip-link {
34 position: absolute;
35 top: -100%;
36 left: 1rem;
37 padding: 0.5rem 1rem;
38 background: #4f46e5;
39 color: white;
40 z-index: 9999;
41}
42
43.skip-link:focus-visible {
44 top: 1rem;
45 outline: 2px solid white;
46 outline-offset: 2px;
47}Dark Mode#
1/* Light mode focus */
2:root {
3 --focus-color: #4f46e5;
4 --focus-ring-color: rgba(79, 70, 229, 0.3);
5}
6
7/* Dark mode focus */
8@media (prefers-color-scheme: dark) {
9 :root {
10 --focus-color: #818cf8;
11 --focus-ring-color: rgba(129, 140, 248, 0.4);
12 }
13}
14
15/* Apply focus styles */
16button:focus-visible,
17a:focus-visible,
18input:focus-visible {
19 outline: 2px solid var(--focus-color);
20 outline-offset: 2px;
21}
22
23/* High contrast for better visibility in dark mode */
24@media (prefers-color-scheme: dark) {
25 .btn:focus-visible {
26 box-shadow:
27 0 0 0 2px #1f2937,
28 0 0 0 4px #818cf8;
29 }
30}High Contrast Mode#
1/* Windows High Contrast Mode support */
2@media (forced-colors: active) {
3 button:focus-visible {
4 outline: 3px solid CanvasText;
5 outline-offset: 3px;
6 }
7
8 /* Don't use box-shadow in high contrast */
9 .btn:focus-visible {
10 box-shadow: none;
11 outline: 3px solid CanvasText;
12 }
13}
14
15/* Ensure visibility in any color scheme */
16.interactive:focus-visible {
17 outline: 2px solid currentColor;
18 outline-offset: 2px;
19}Focus Within Patterns#
1/* Dropdown menu */
2.dropdown:focus-within .dropdown-menu {
3 display: block;
4 opacity: 1;
5 visibility: visible;
6}
7
8/* Search box expansion */
9.search-container:focus-within {
10 width: 300px;
11}
12
13.search-container:focus-within .search-suggestions {
14 display: block;
15}
16
17/* Form field label */
18.floating-label {
19 position: relative;
20}
21
22.floating-label input:focus + label,
23.floating-label input:not(:placeholder-shown) + label {
24 transform: translateY(-1.5rem) scale(0.85);
25 color: #4f46e5;
26}
27
28/* Card hover/focus state */
29.card:hover,
30.card:focus-within {
31 transform: translateY(-2px);
32 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
33}Modal and Dialog#
1/* Modal focus trap indication */
2.modal:focus-visible {
3 outline: none;
4}
5
6.modal[open] {
7 animation: modal-open 0.3s ease;
8}
9
10/* Close button focus */
11.modal-close:focus-visible {
12 outline: 2px solid white;
13 outline-offset: 2px;
14 background: rgba(255, 255, 255, 0.2);
15}
16
17/* Dialog focus */
18dialog:focus-visible {
19 outline: none;
20 box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.5);
21}
22
23/* Focus visible inside modal */
24.modal *:focus-visible {
25 outline: 2px solid #4f46e5;
26 outline-offset: 2px;
27}Accessibility Best Practices#
1/* Never do this without replacement */
2/* BAD: Removes focus for everyone */
3*:focus {
4 outline: none;
5}
6
7/* GOOD: Replace with visible alternative */
8*:focus-visible {
9 outline: 2px solid #4f46e5;
10 outline-offset: 2px;
11}
12
13/* Minimum focus indicator size */
14/* WCAG 2.2 requires visible focus indicator */
15button:focus-visible {
16 /* At least 2px outline or equivalent */
17 outline: 2px solid #4f46e5;
18
19 /* Or sufficient contrast area */
20 box-shadow: 0 0 0 3px #4f46e5;
21}
22
23/* Sufficient contrast */
24/* Focus indicator should have 3:1 contrast ratio */
25.dark-button:focus-visible {
26 outline: 2px solid white; /* High contrast against dark bg */
27}
28
29.light-button:focus-visible {
30 outline: 2px solid #1f2937; /* High contrast against light bg */
31}Browser Support Fallback#
1/* Fallback for older browsers */
2button:focus {
3 outline: 2px solid #4f46e5;
4 outline-offset: 2px;
5}
6
7/* Modern browsers that support :focus-visible */
8@supports selector(:focus-visible) {
9 button:focus {
10 outline: none;
11 }
12
13 button:focus-visible {
14 outline: 2px solid #4f46e5;
15 outline-offset: 2px;
16 }
17}
18
19/* Or use the what-input library polyfill approach */
20[data-whatintent="mouse"] *:focus {
21 outline: none;
22}Best Practices#
Accessibility:
✓ Always provide visible focus indicators
✓ Ensure 3:1 contrast ratio
✓ Test with keyboard navigation
✓ Support high contrast mode
Design:
✓ Use :focus-visible for keyboard focus
✓ Match focus style to brand
✓ Use consistent focus patterns
✓ Consider dark/light modes
Implementation:
✓ Test in all browsers
✓ Provide fallbacks
✓ Use outline-offset for spacing
✓ Consider animation preferences
Avoid:
✗ Removing outline without replacement
✗ Low contrast focus indicators
✗ Inconsistent focus styles
✗ Ignoring form elements
Conclusion#
Focus styling is crucial for accessibility. Use :focus-visible for keyboard-only focus indicators, ensure sufficient contrast, and provide consistent patterns across your site. Never remove focus styles without providing an equally visible alternative.