# Invisible focus ring: the causes and the CSS fix

A global reset with `outline: none` removes the focus indicator and puts nothing back. Use `:focus-visible` with an outline and offset, and check overflow.

> Page: https://ui.eneawork.it/blog/focus-ring-invisibile
> Markdown: https://ui.eneawork.it/blog/focus-ring-invisibile.md

*2026-11-01*

Risposta breve: un reset globale con outline: none cancella l'indicatore di focus. Sostituiscilo con :focus-visible { outline: 2px solid; outline-offset: 2px }: la pagina torna usabile da tastiera e il mouse non vede cambiamenti.

Il tag accessibility conta 8.264 domande sulla Stack Exchange API al 31 ottobre 2026. Un indicatore di focus assente è tra i difetti più segnalati dagli audit automatici. Il catalogo EneUI misura 111.985 componenti, 1.556 blocchi, 12.808 icone da 1.472 librerie, verificati su /api/stats il 1 novembre 2026. Ogni comando qui sotto è eseguibile in locale: nessun passaggio richiede un account.

## Come il browser disegna l'indicatore

outline viene dipinto fuori dal box del bordo, in un passaggio separato, e non partecipa al layout: aggiungerlo o toglierlo non sposta nulla. box-shadow è un'ombra dell'elemento, e il contenitore con overflow: hidden la taglia insieme al contenuto. In forced-colors la UA rimappa i colori di sistema: l'outline diventa Highlight e resta visibile, uno sfondo o un'ombra personalizzati vengono sostituiti.

:focus-visible non è una media query sulla tastiera, è un'euristica della UA. In generale l'indicatore appare quando l'ultima interazione era da tastiera, e appare sempre sui campi di testo e sugli elementi che ricevono il focus da script dopo un comando da tastiera. È una scelta del browser, non una garanzia dello standard: per questo va verificata, non dedotta.

## Le tre cause

## La correzione CSS

:focus-visible { outline: 2px solid var(--ui-focus, #2563eb); outline-offset: 2px; border-radius: 4px; } /* il mouse non deve vedere il ring */ :focus:not(:focus-visible) { outline: none; }

outline è preferibile a box-shadow perché non occupa spazio e non viene tagliato da overflow: hidden. L'offset stacca il bordo dal contenuto e lo rende visibile anche su sfondi colorati.

## La variante Tailwind

Salva

focus-visible: è la variante corretta: focus: si attiva anche al click e rende il feedback rumoroso. Se nel progetto esiste già un token di focus, usa quello invece di un colore fisso.

## Il passo diagnostico

Il controllo utile non è leggere il foglio di stile, è elencare i controlli che restano senza indicatore. Incolla questo blocco nella console e premi Tab nella pagina per verificare i casi trovati.

[...document.querySelectorAll("a[href],button,input,select,textarea,[tabindex]")] .filter((el) => { el.focus(); const s = getComputedStyle(el); return s.outlineStyle === "none" || parseFloat(s.outlineWidth) === 0; }) .map((el) => el.tagName.toLowerCase() + (el.className ? "." + el.className : ""));

L'elenco contiene i candidati, non la diagnosi: un elemento può avere l'indicatore sul wrapper con :focus-within. Se invece l'elenco è vuoto e il ring non si vede, il problema è il contrasto o il clipping, non la regola.

## Quando la correzione non funziona

Se il focus non si vede su un bottone dentro un contenitore con overflow: hidden, il problema è il clipping: passa a outline o sposta l'overflow. In modalità a contrasto elevato il browser sostituisce i colori: un box-shadow sparisce, un outline no. Se il focus non arriva proprio, cerca tabindex="-1" o un pointer-events: none sul controllo.

## Quando la correzione comune peggiora le cose

Per far stare il ring dentro una card con overflow: hidden la correzione tipica è togliere l'overflow al contenitore. Il bordo torna visibile e il layout perde il suo contratto: la tabella larga esce dalla pagina, il carosello mostra la slide successiva, lo scorrimento orizzontale compare sul body. Su mobile il danno supera il difetto.

L'altra scorciatoia è sostituire l'indicatore con un cambio di background-color o di border-color. Funziona a schermo normale e sparisce in forced-colors, dove la UA forza sfondo e colori di sistema: chi usa il contrasto elevato perde l'unico segnale. Se devi cambiare qualcosa, cambia outline-color, che viene rimappato.

## Un limite da mettere nel conto

L'indicatore appartiene al singolo elemento: non puoi disegnare un ring unico attorno a un widget composito come una listbox o una toolbar. Il massimo che ottieni è lo stile sul contenitore con :focus-within, che però non dice quale voce è attiva. Per quello serve un indicatore interno, e nessuna proprietà CSS lo genera da sola.

## Come prevenirlo

## Dove continuare

Nel catalogo: form di login, componenti e blocchi. Le guide: checklist accessibilità e palette con contrasto misurato; il confronto: Radix, Base UI e React Aria.

## FAQ

### `focus:` e `focus-visible:` sono equivalenti?

No. `focus:` si attiva anche al click del mouse; `focus-visible:` solo quando il browser ritiene che il focus sia da tastiera.

### Perché il ring non si vede su un bottone con overflow?

Il ring viene disegnato fuori dal box e il contenitore con `overflow: hidden` lo taglia. Passa a `outline` con `outline-offset` o cambia il contenitore.

### Quale contrasto serve?

WCAG 2.2 chiede un indicatore con contrasto 3:1 rispetto ai colori adiacenti e area minima. Un ring da 1px spesso non basta.

### Come funziona in forced-colors mode?

Il browser sostituisce i colori. Se rimuovi l'outline, in modalità a contrasto elevato non resta nulla: usa `outline`, non `box-shadow`.

### Devo testarlo su ogni componente?

Almeno su link, bottoni, input, select, menu e dialog. Sono i punti in cui il focus si perde più spesso.
