Back to Blog
CSSAccessibilityFocusUX

CSS :focus-visible and Outline Styling

Master CSS focus styles with :focus-visible. From accessible outlines to custom focus indicators.

B
Bootspring Team
Engineering
July 28, 2020
6 min read

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}
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}
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.

Share this article

Help spread the word about Bootspring