Focus Ring

FrontAlign's components — Button, Dropdown, Input, and others — have always shipped with a deliberate keyboard focus state: a colored outline plus a soft glow, tuned per semantic color. Focus Ring utilities take that exact styling out of the components and make it available on anything you build yourself, from a plain link to a <div tabindex="0"> acting as a custom card.

<button class="btn focus-ring">Save changes</button>
<div tabindex="0" class="custom-card focus-ring-info">...</div>

Your own interactive elements end up indistinguishable from native FrontAlign components at the one moment accessibility actually depends on it: when someone is navigating by keyboard.

Why :focus-visible, Not :focus

Every Focus Ring class is scoped to :focus-visible rather than :focus. The distinction matters: :focus-visible only matches when the browser determines the user is navigating by keyboard or assistive technology — a mouse click or a tap doesn't trigger it. This mirrors how modern browsers already draw their own native focus indicators, so a sighted mouse user never sees a ring appear on click, while a keyboard user always does.

.focus-ring:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px oklch(from var(--accent) l c h / 0.25);
}

Anatomy of the Ring

Each variant combines two layers, deliberately kept separate:

  • outline — a crisp, 2px solid line, offset 2px from the element's edge. Because outline (unlike box-shadow) is respected by forced-colors / Windows High Contrast Mode, the ring never silently disappears for users relying on that setting.
  • box-shadow — a soft 4px glow at 25% opacity, generated directly from the same color token via the oklch(from ...) relative color syntax. There's no separate -alpha or -glow token to keep in sync — the glow is always exactly the outline color, just translucent.

The result reads as one cohesive ring, but survives edge cases (high-contrast mode, dark backgrounds, colored surfaces) that a single box-shadow-only approach tends to fail.

Available Variants

Five variants ship out of the box, each mapped to one of FrontAlign's semantic color tokens.

ClassTokenUse for
.focus-ring--accentGeneral-purpose focus state — links, inputs, default buttons
.focus-ring-danger--dangerDestructive actions — delete, remove, revoke access
.focus-ring-warning--warningActions that need a second look — irreversible but non-destructive
.focus-ring-success--successConfirming actions — save, approve, submit
.focus-ring-info--infoInformational triggers — tooltip anchors, help icons

Customizing the Ring Color

Because every variant reads its color directly from a theme token rather than a hardcoded value, changing the ring project-wide is a theme change, not a CSS override.

export default {
  theme: {
    accent: '#7c3aed',
    danger: '#dc2626'
  }
}

Note that .focus-ring reads --accent, not --primary — the two tokens are intentionally kept separate so your brand's primary action color and its focus-indication color can differ. If you want them to match, set accent to the same value as primary in your theme.

Using It on Non-Interactive Elements

Any element can receive keyboard focus and a matching ring, as long as it's given a tabindex:

<div
  tabindex="0"
  role="button"
  class="custom-card focus-ring"
  onclick="handleSelect()"
>
  <h3>Pro Plan</h3>
  <p>$29/month</p>
</div>

Pairing tabindex="0" with role="button" (and a matching keydown handler for Enter/Space in your own JS) makes a <div> fully keyboard-operable — the Focus Ring utility then ensures it's also keyboard-visible.

FrontAlign