Zazz Design Framework
Reference

Focus Ring

A primary-colored ring with a background-gap halo. One token, one utility, one focus pattern across every primitive.

Class reference

ClassProperty
ringbox-shadow: var(--focus-ring)

The ring utility applies the --focus-ring token. Use it on :focus-visible so keyboard navigation shows the ring without mouse clicks triggering it.

Token

--focus-ring:
  0 0 0 4px var(--background),
  0 0 0 2px var(--primary);

Two layered box-shadows:

  • Inner ring — 2px of --primary. The visible indicator.
  • Outer gap — 4px of --background. Separates the ring from any border the element already has so the two don't fight.

Both layers reference theme tokens, so the ring adapts to mode automatically. Primary lightens in dark mode; the gap flips from off-white to near-black.

Usage

Apply box-shadow: var(--focus-ring) (or the ring utility) on :focus-visible:

.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

Or directly with the utility class:

.btn:focus-visible {
  @apply ring;
}

The Zazz primitives (Button, Badge, Input, and friends) all consume --focus-ring for their focus state, so a single token tunes the ring everywhere.

:focus vs :focus-visible

Use :focus-visible, not :focus. The ring should appear on keyboard navigation, not on mouse clicks. :focus-visible is the browser's mechanism for that distinction.

.btn:focus-visible {
  box-shadow: var(--focus-ring);
}

.btn:focus:not(:focus-visible) {
  /* mouse click: no ring */
  outline: none;
  box-shadow: none;
}

Modern browsers handle the distinction automatically when you use :focus-visible. You generally don't need the secondary :focus:not(:focus-visible) rule.

Customizing

Retune the ring for the whole product by overriding the token:

:root {
  --focus-ring:
    0 0 0 3px var(--background),
    0 0 0 2px var(--destructive); /* destructive ring for delete buttons */
}

For a high-contrast accessibility mode, increase the spread:

.high-contrast {
  --focus-ring:
    0 0 0 6px var(--background),
    0 0 0 4px var(--foreground);
}

Components that already consume --focus-ring follow the override automatically.

Why box-shadow instead of outline

outline doesn't respect border-radius, so a rounded button would get a rectangular outline. box-shadow follows the element's shape exactly. The trade-off is that box-shadow doesn't push other elements aside the way outline-offset does — the gap layer in --focus-ring handles that by painting a --background-colored ring that visually separates the indicator from any border.

On this page