# Draw Focus Rings with box-shadow

**SHOULD** · **ID:** `interactions-focus-ring-shadow` · **Category:** interactions
**Source:** [Rauno](https://interfaces.rauno.me/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-focus-ring-shadow

> SHOULD: Draw focus rings with `box-shadow` (Tailwind `ring-*`), not `outline` — outline ignores `border-radius` before Safari 16.4, so rounded controls get a rectangle.

Build the focus ring out of box-shadow rather than outline, so it follows the border radius

This is about the mechanism, not about having a ring at all. `outline` is painted outside the border box and, on any browser older than Safari 16.4, ignores `border-radius` entirely — so a pill or heavily rounded control gets a hard rectangle around it. `box-shadow` (Tailwind's `ring-*`) is clipped to the element's own radius in every engine, and it composes: you can stack a background-coloured offset shadow under a coloured ring to keep the focus state legible on any surface. Since plenty of users never update their OS, treat the box-shadow ring as the portable default.

## Rule snippet

```tsx
:focus-visible { box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring); }
```

## Bad — do not do this

`interactions-focus-ring-shadow-bad`

```tsx
import { useState } from 'react';

export function FocusRingShadowBad() {
  const [focused, setFocused] = useState(false);

  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-6">
        <p className="text-xs text-muted-foreground mb-6">
          Tab into the pill. The ring is drawn with <code>outline</code>. The dashed box is the same
          <code> outline</code> as an un-updated Safari (pre-16.4) paints it: a rectangle that ignores the radius.
        </p>

        <div className="flex justify-center py-6">
          <div className="relative">
            {/* Faithful reproduction of the legacy outline box: square corners, hugging the border box. */}
            {focused && (
              <span
                aria-hidden
                className="pointer-events-none absolute -inset-[3px] border-2 border-dashed border-ring"
              />
            )}
            <button
              onFocus={() => setFocused(true)}
              onBlur={() => setFocused(false)}
              // outline for the focus ring: the property that historically squares off rounded corners.
              className="px-6 py-2.5 rounded-full bg-primary text-primary-foreground font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
            >
              Rounded button
            </button>
          </div>
        </div>

        <p className="text-xs text-muted-foreground">
          Corners of the ring do not follow the pill. On any OS that never got Safari 16.4 the ring is a hard rectangle.
        </p>
      </div>
      <p className="text-xs text-error mt-4">
        <code>outline</code> does not reliably follow <code>border-radius</code> — the ring squares off the pill
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-focus-ring-shadow-good`

```tsx
export function FocusRingShadowGood() {
  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-6">
        <p className="text-xs text-muted-foreground mb-6">
          Tab into the pill. The ring is a <code>box-shadow</code> (Tailwind&apos;s <code>ring</code>), which is
          clipped to the element&apos;s own radius in every browser.
        </p>

        <div className="flex justify-center py-6">
          <button
            // ring-* compiles to box-shadow, so the ring inherits rounded-full.
            // ring-offset-background paints the gap so the ring reads on any surface.
            className="px-6 py-2.5 rounded-full bg-primary text-primary-foreground font-medium outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
          >
            Rounded button
          </button>
        </div>

        <p className="text-xs text-muted-foreground">
          The ring hugs the pill exactly, and the offset gap is rounded too — no browser-version caveats.
        </p>
      </div>
      <p className="text-xs text-success mt-4">
        <code>box-shadow</code> rings always respect <code>border-radius</code> — same ring everywhere
      </p>
    </div>
  );
}
```

## References

- [interfaces.rauno.me](https://interfaces.rauno.me/)
- [Safari 16.4 release notes (outline follows radius)](https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes)
- [MDN: box-shadow](https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow)
