🚀AdvancedAdvanced CSS Architecture

CSS Accessibility Features

Write inclusive CSS using focus styles, prefers-reduced-motion, forced-colors, skip links, and visually-hidden utilities.

Overview

Accessible CSS ensures your UI is usable by people with visual, motor, and cognitive disabilities. Key areas: **Focus styles** visible keyboard navigation: ```css :focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; } /* Remove focus ring for mouse users only */ :focus:not(:focus-visible) { outline: none; } ``` **Motion reduction** for vestibular disorders: ```css @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } ``` **Visually hidden but accessible:** ```css .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } ``` **Forced colors** for high-contrast mode: ```css @media (forced-colors: active) { .btn { border: 2px solid ButtonText; } } ```

Key Points

  • •:focus-visible applies focus styles only for keyboard navigation, not mouse click
  • •Never use outline: none without providing an alternative focus indicator
  • •prefers-reduced-motion: reduce disables animations for users with vestibular disorders
  • •.sr-only hides content visually while keeping it accessible to screen readers
  • •forced-colors: active indicates Windows High Contrast Mode; use system color keywords there
  • •prefers-contrast: more can be used for users who prefer higher contrast without forced colors
  • •Color alone is never sufficient to convey information; always pair color with shape, text, or pattern
  • •Focus management: after a modal opens, move focus inside it; after it closes, return focus to the trigger

Examples

Focus indicators for keyboard navigation

/* Global visible focus ring */
:focus-visible {
  outline: 2.5px solid #6366f1;
  outline-offset: 3px;
  border-radius: 3px;
}

/* Custom focus ring for buttons */
.btn:focus-visible {
  outline: 2.5px solid #6366f1;
  outline-offset: 3px;
  box-shadow: 0 0 0 5px rgba(99,102,241,0.2);
}

/* Links: ensure focus is distinct from hover */
a:focus-visible {
  outline: 2.5px solid #6366f1;
  outline-offset: 2px;
  text-decoration: underline;
}

/* Form inputs */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2.5px solid #6366f1;
  outline-offset: 0;
  border-color: #6366f1;
  box-shadow: 0 0 0 3px rgba(99,102,241,0.15);
}

:focus-visible is the modern approach: focus ring shows for keyboard but not mouse, matching native browser behavior.

Motion reduction

/* For users who prefer no motion */
@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;
  }
}

/* Better approach: opt in to motion, not opt out */
.card {
  /* No animation by default */
}

@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }
  .card:hover {
    transform: translateY(-4px);
  }
}

The opt-in approach (@media no-preference) is more explicit: animations only run when the user has not asked to reduce motion.

Screen reader utilities

/* Visually hidden but screen-reader accessible */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Reveal on focus (for skip navigation links) */
.sr-only-focusable:focus-visible {
  position: static;
  width: auto;
  height: auto;
  padding: 8px 16px;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
}

/* Skip to main content link */
.skip-link {
  position: absolute;
  top: -100%;
  left: 8px;
  z-index: 9999;
  background: #6366f1;
  color: white;
  padding: 8px 16px;
  border-radius: 0 0 8px 8px;
  font-weight: 600;
  text-decoration: none;
  transition: top 0.2s;
}

.skip-link:focus {
  top: 0;
}

High contrast mode (forced-colors)

/* Default styles */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 99px;
  background: #dcfce7;
  color: #15803d;
}

.status-badge::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

/* High contrast: use system colors, ensure border */
@media (forced-colors: active) {
  .status-badge {
    background: Canvas;
    color: CanvasText;
    border: 2px solid CanvasText;
    forced-color-adjust: none;
  }

  .status-badge::before {
    background: Highlight;
  }
}

System color keywords (Canvas, CanvasText, ButtonFace, Highlight) map to OS high-contrast theme colors.

Common Mistakes

  • Removing outline from :focus without providing an alternative (makes keyboard navigation invisible)
  • Using only color to convey state (error states should also use an icon or text, not just red)
  • Animating content without checking prefers-reduced-motion (can cause vertigo for vestibular disorder users)
  • Using display: none or visibility: hidden for sr-only content (both hide from screen readers too)

Note

Test keyboard navigation regularly: tab through your UI and verify that you can always see where focus is. A user who cannot use a mouse depends on this entirely.

Exercises

Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.

Loading editor…

Completed: 0 / 2 exercises

Knowledge Check

Question 1 of 3

What is the difference between :focus and :focus-visible?