# Group Focus with :focus-within

**MUST** · **ID:** `interactions-focus-within-group` · **Category:** interactions
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-focus-within-group

> MUST: Ring the wrapper of a compound control with `:focus-within` (and `outline: none` on the children), keeping a distinct style on the focused child — do not ring only the inner `<input>`.

Style focus on the wrapper of a compound control so the whole control lights up, not one child

A compound control — an input with a currency adornment and a "Max" button inside one bordered field — is a single thing to the user but several elements to the DOM. Ringing only the inner `<input>` draws a ring floating inside the box, and focusing the trailing button lights up nothing at all. `:focus-within` matches an element when focus lands on it or on any descendant, so putting the ring and border highlight on the wrapper (and `outline: none` on the children) makes the visual control and the focus affordance agree. Keep a distinct style on the focused child too, so users know which part of the group has focus.

## Rule snippet

```tsx
.field:focus-within { outline: 2px solid var(--ring); outline-offset: 2px; }
.field :is(input, button):focus-visible { outline: none; background: var(--muted); }
```

## Bad — do not do this

`interactions-focus-within-group-bad`

```tsx
export function FocusWithinGroupBad() {
  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <p className="text-xs text-muted-foreground mb-4">
          Tab through the field. Only the bare <code>&lt;input&gt;</code> lights up — a ring floating inside the box.
          Focus the &ldquo;Max&rdquo; button and the field gives no sign it is active at all.
        </p>

        <label htmlFor="bad-amount" className="block text-sm font-medium text-foreground mb-1.5">
          Amount
        </label>

        {/* The wrapper is the visual control, but nothing on it reacts to focus. */}
        <div className="flex items-center gap-2 rounded-lg border border-input bg-background px-3 py-2">
          <span className="text-sm text-muted-foreground">$</span>
          <input
            id="bad-amount"
            type="text"
            inputMode="decimal"
            defaultValue="1,250.00"
            // The ring is on the inner input only, so it hugs the text box, not the control.
            className="min-w-0 flex-1 bg-transparent text-sm text-foreground focus:outline-hidden focus:ring-2 focus:ring-ring rounded"
          />
          <button
            type="button"
            className="shrink-0 rounded px-2 py-0.5 text-xs font-medium text-muted-foreground hover:bg-muted"
          >
            Max
          </button>
        </div>
      </div>
      <p className="text-xs text-error mt-4">
        The ring is trapped on one child — the compound control never reads as focused
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-focus-within-group-good`

```tsx
export function FocusWithinGroupGood() {
  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <p className="text-xs text-muted-foreground mb-4">
          Tab through the field. The whole control lights up whether focus lands on the input or on
          &ldquo;Max&rdquo;, because the ring lives on the wrapper.
        </p>

        <label htmlFor="good-amount" className="block text-sm font-medium text-foreground mb-1.5">
          Amount
        </label>

        {/* :focus-within promotes focus on any descendant to the wrapper, which is the real control. */}
        <div className="flex items-center gap-2 rounded-lg border border-input bg-background px-3 py-2 transition-shadow focus-within:border-ring focus-within:ring-2 focus-within:ring-ring">
          <span className="text-sm text-muted-foreground">$</span>
          <input
            id="good-amount"
            type="text"
            inputMode="decimal"
            defaultValue="1,250.00"
            // Children suppress their own rings; the group owns the focus affordance.
            className="min-w-0 flex-1 bg-transparent text-sm text-foreground outline-hidden"
          />
          <button
            type="button"
            className="shrink-0 rounded px-2 py-0.5 text-xs font-medium text-muted-foreground outline-hidden hover:bg-muted focus-visible:bg-muted focus-visible:text-foreground"
          >
            Max
          </button>
        </div>
      </div>
      <p className="text-xs text-success mt-4">
        <code>:focus-within</code> on the wrapper makes the whole compound control read as one focused thing
      </p>
    </div>
  );
}
```

## References

- [Vercel Web Interface Guidelines](https://github.com/vercel-labs/web-interface-guidelines/blob/main/command.md)
- [MDN: :focus-within](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within)
