Skip to content

Focus Indicators — Designing Visible Focus States in Design Systems

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Focus Indicators. We cover key concepts, practical examples, and best practices to help you master this topic.

Focus indicators in design systems must be visible, high-contrast, consistent across all components, use focus-visible for smart appearance, and be defined as design tokens for system-wide consistency.

What You'll Learn

You will learn how to design accessible focus indicators, how to use CSS focus-visible for the right behavior, how to define focus tokens, and how to ensure every interactive component has a visible focus state.

Why It Matters

Users who navigate with a keyboard rely on focus indicators to know where they are on the page. Without visible focus indicators, keyboard users cannot operate the interface effectively.

Real-World Use

DodaKit defines focus indicators as design tokens. Every component uses the same focus ring style. The focus ring uses outline not box-shadow to respect platform conventions and avoid clipping.

flowchart TD
  A[Focus Indicators] --> B[Visible]
  A --> C[High Contrast]
  A --> D[Consistent]
  A --> E[focus-visible]
  A --> F[Design Token]
  B --> G[2-3px outline]
  C --> H[3:1 minimum on all backgrounds]
  D --> I[Same style everywhere]
  E --> J[Shows only on keyboard]
  F --> K[Token for reuse]

The focus-visible Pseudo-class

Use :focus-visible instead of :focus. The :focus-visible pseudo-class applies focus styles only when the browser determines the focus should be visible, typically keyboard navigation. It does not show on mouse clicks.

/* Focus indicator system */
:root {
  --ds-focus-ring-width: 3px;
  --ds-focus-ring-style: solid;
  --ds-focus-ring-color: #005fcc;
  --ds-focus-ring-offset: 2px;
  --ds-focus-ring: var(--ds-focus-ring-width) var(--ds-focus-ring-style) var(--ds-focus-ring-color);
}

/* Apply to all interactive elements */
*:focus-visible {
  outline: var(--ds-focus-ring);
  outline-offset: var(--ds-focus-ring-offset);
}

/* High contrast mode override */
@media (prefers-contrast: more) {
  *:focus-visible {
    outline-color: #000000;
    outline-width: 4px;
  }
}

/* Reduced motion: no transition on focus */
@media (prefers-reduced-motion: reduce) {
  *:focus-visible {
    transition: none;
  }
}

Focus Indicator Design

Size

A focus ring should be 2 to 3 pixels wide. Thinner rings may not be visible. Thicker rings obscure the element.

Color

Use a high-contrast color. Blue (#005fcc) works on most backgrounds. Ensure 3:1 contrast against all adjacent colors.

Offset

A 2 to 4 pixel offset prevents the focus ring from blending into the component border.

Shape

Use outline for focus rings. Box-shadow can clip on overflow hidden containers. Outline respects platform conventions.

// Focus indicator validator
function validateFocusIndicator(styles) {
  const checks = [];

  // Check if focus-visible is used
  if (styles.focusSelector === ':focus-visible') {
    checks.push({ check: 'Uses :focus-visible', pass: true });
  } else if (styles.focusSelector === ':focus') {
    checks.push({ check: 'Uses :focus instead of :focus-visible', pass: false,
      note: ':focus shows on mouse click too' });
  } else {
    checks.push({ check: 'Has focus selector', pass: false,
      note: 'No focus selector found' });
  }

  // Check outline width
  const outlineWidth = parseFloat(styles.outlineWidth);
  if (outlineWidth >= 2 && outlineWidth <= 4) {
    checks.push({ check: 'Outline width 2-4px', pass: true });
  } else {
    checks.push({ check: 'Outline width 2-4px', pass: false,
      note: `Width is ${outlineWidth}px` });
  }

  // Check for outline removal without replacement
  if (styles.outline === 'none' && !styles.customIndicator) {
    checks.push({ check: 'Has replacement for removed outline', pass: false,
      note: 'Outline removed without replacement' });
  }

  // Check box-shadow not used as only indicator
  if (styles.usesBoxShadow && !styles.usesOutline) {
    checks.push({ check: 'Not using only box-shadow', pass: false,
      note: 'Box-shadow clips on overflow hidden' });
  }

  const passed = checks.filter(c => c.pass).length;
  return {
    total: checks.length,
    passed: passed,
    failed: checks.length - passed,
    details: checks,
    overall: checks.length === passed ? 'Pass' : 'Issues found'
  };
}

const buttonStyles = {
  focusSelector: ':focus-visible',
  outlineWidth: '3px',
  outline: '3px solid #005fcc',
  customIndicator: true,
  usesBoxShadow: false,
  usesOutline: true
};

console.log(validateFocusIndicator(buttonStyles));

Expected output:

{
  total: 4,
  passed: 4,
  failed: 0,
  details: [
    { check: 'Uses :focus-visible', pass: true },
    { check: 'Outline width 2-4px', pass: true },
    { check: 'Has replacement for removed outline', pass: true },
    { check: 'Not using only box-shadow', pass: true }
  ],
  overall: 'Pass'
}

Focus Within and Focus Ring

For Composite components, use :focus-within to show focus on the container when any child element has focus. This helps users identify which component group is focused.

<!-- Focus indicator examples -->
<button class="ds-button ds-button--primary" type="button">
  Scan (focus-visible)
</button>

<div class="ds-input-group" aria-label="Search">
  <label for="search-input" class="visually-hidden">Search</label>
  <input id="search-input" type="search" placeholder="Search...">
  <button type="submit" aria-label="Submit search">Go</button>
</div>

<style>
  /* Component focus states */
  .ds-button:focus-visible {
    outline: var(--ds-focus-ring);
    outline-offset: var(--ds-focus-ring-offset);
  }

  /* Input group focus-within */
  .ds-input-group:focus-within {
    outline: var(--ds-focus-ring);
    outline-offset: 2px;
    border-radius: 4px;
  }

  /* Never remove focus without replacement */
  *:focus {
    outline: none; /* Only if :focus-visible provides replacement */
  }
</style>

Common Mistakes

1. Removing Focus Outlines

The most common Accessibility mistake. outline: none without a replacement makes the site unusable for keyboard users.

2. Using :focus Instead of :focus-visible

Using :focus applies focus styles on mouse clicks too. This confuses users who see a focus ring after every click.

3. Focus Ring Not Visible on All Backgrounds

A blue focus ring works on white but may be invisible on a blue background. Test on all component variants.

4. Box-Shadow Focus That Clips

Components with overflow: hidden clip box-shadow. Use outline instead for reliable focus indicators.

5. No Focus on Interactive Containers

Interactive containers like cards with onclick need focus indicators. Use tabindex="0" and a focus style.

6. Inconsistent Focus Styles

Different focus styles on different components confuse users. Use a single focus ring token everywhere.

7. Focus Ring Too Thin

A 1px focus ring is not visible enough. Use at least 2px, preferably 3px.

Practice Questions

1. What CSS pseudo-class should be used for focus indicators?

:focus-visible. It applies focus styles only when the browser determines focus should be visible, typically during keyboard navigation.

2. Why should outline be used instead of box-shadow for focus?

Outline does not clip on overflow: hidden containers. Box-shadow can be clipped.

3. What is the recommended focus ring width?

2 to 3 pixels. Thinner rings are not visible enough. Thicker rings obscure the element.

4. What is :focus-within used for?

To show a focus indicator on a parent container when any child element has focus. Useful for composite components.

5. Challenge: Design a focus indicator system with 3 variants (default, dark background, high contrast). Test each on 5 different background colors.

FAQ

Can I use a different focus indicator for different components?

Consistency is more accessible. Use the same focus ring style for all interactive elements.

Does :focus-visible work in all browsers?

Yes. All modern browsers support :focus-visible since 2022. Include a :focus fallback for older browsers.

Should I use border or outline for focus?

Use outline. Border changes layout. Outline overlays without affecting layout.

How do I handle focus in dark mode?

Use a light-colored focus ring for dark mode. A token system with light and dark variants handles this automatically.

Can I animate the focus ring?

Use transition on the focus property. Respect prefers-reduced-motion and disable animation if requested.

Mini Project

Create a focus indicator system for a design system with 8 interactive components. Define focus tokens, apply focus-visible, test on light and dark backgrounds, and document the system.

What's Next

Learn about Accessible Forms System patterns for accessible form components. Then explore Accessible Navigation System.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro