Je veux désactiver :focus quand ce n'est pas nécessaire parce que je n'aime pas à quoi ressemble ma navigation lorsque le focus est dessus. Il utilise le même style que .active et c'est déroutant. Cependant, je ne veux pas m'en débarrasser pour les personnes qui utilisent le clavier.

Je pensais ajouter une classe enabled-focus sur le corps de la tabulation et avoir ensuite body.enabled-focus a:focus{...} mais cela ajouterait beaucoup de CSS supplémentaires pour chaque élément qui a le focus. Supprimez ensuite cette classe du corps à la première souris enfoncée.

Comment pourrais-je m'y prendre? Y a-t-il une meilleure solution?

105
Miro 14 juil. 2015 à 12:19

8 réponses

Meilleure réponse

Cet excellent article par Roman Komarov constitue une solution viable pour obtenir des styles de focus clavier uniquement pour les boutons , liens et d'autres éléments de conteneur tels que les étendues ou les divs (qui sont artificiellement rendus focalisables avec l'attribut tabindex)

La solution:

button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-size: inherit;
}

.btn {
  all: initial;
  margin: 1em;
  display: inline-block; 
}

.btn__content {
  background: orange;
  padding: 1em;
  cursor: pointer;
  display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
    position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content  {
    background: salmon;
}

.btn:active > .btn__content  {
    background: darkorange;
}

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8;
    color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
    outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

<a class="btn" href="#x">
    <span class="btn__content" tabindex="-1">
        I'm a link!
    </span>
</a>

<span class="btn" tabindex="0">
    <span class="btn__content" tabindex="-1">
        I'm a span!
    </span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing - behold - focus styles</p>

Codepen

1) Enveloppez le contenu de l'élément interactif d'origine dans un élément interne supplémentaire avec tabindex="-1" (voir explication ci-dessous)

Donc au lieu de dire:

<button id="btn" class="btn" type="button">I'm a button!</button>

Faites ceci:

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

2) Déplacez le style CSS vers l'élément interne (la disposition CSS doit rester sur l'élément externe d'origine) - de sorte que la largeur / hauteur de l'élément externe provienne de l'élément interne, etc.

3) Supprimez le style de mise au point par défaut des éléments externes et internes:

.btn:focus,
.btn__content:focus {
    outline: none;
}

4) Ajoutez un style de focus à l'élément interne uniquement lorsque l'élément externe a le focus:

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles */
    color: lime; /* keyboard-only focus styles */
} 

Pourquoi ça marche?

L'astuce consiste ici à définir l'élément intérieur avec tabindex="-1" - voir MDN:

Une valeur négative (généralement tabindex = "- 1" signifie que l'élément doit être focalisable, mais ne doit pas être accessible via la navigation séquentielle au clavier ...

L'élément est donc focalisable via les clics de souris ou par programme, mais d'un autre côté - il ne peut pas être atteint via les "onglets" du clavier.

Ainsi, lorsque l'élément interactif est cliqué - l'élément intérieur obtient le focus. Aucun style de focus ne s'affichera car nous les avons supprimés.

.btn:focus,
.btn__content:focus {
    outline: none;
}

Notez que seul 1 élément DOM peut être focalisé à un moment donné (et document.activeElement renvoie cet élément) - donc uniquement l'élément interne sera focalisé.

D'un autre côté: lorsque nous tabulons à l'aide du clavier - seul l'élément externe aura le focus (rappelez-vous: l'élément interne a tabindex = "- 1" et n'est pas accessible via la navigation séquentielle au clavier) [Notez que pour les éléments externes intrinsèquement non focalisables comme un <div> cliquable - nous devons les rendre artificiellement focalisables en ajoutant tabindex="0"]

Maintenant, notre CSS entre en jeu et ajoute les styles de focus uniquement au clavier à the inner element.

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8; /* keyboard-only focus styles */
    color: lime; /* keyboard-only focus styles */
} 

Bien sûr, nous voulons nous assurer que lorsque nous tabulons et appuyons sur enter - nous n'avons pas cassé notre élément interactif et le javascript s'exécutera.

Voici une démo pour montrer que c'est effectivement le cas, notez cependant que vous ne l'obtenez que gratuitement (c'est-à-dire en appuyant sur Entrée pour provoquer un événement de clic) pour des éléments intrinsèquement interactifs comme les boutons et les liens ... pour d'autres éléments tels que les étendues - vous devez coder cela manuellement :)

//var elem = Array.prototype.slice.call(document.querySelectorAll('.btn'));
var btns = document.querySelectorAll('.btn');
var fakeBtns = document.querySelectorAll('.btn[tabindex="0"]');


var animate = function() {
  console.log('clicked!');
}

var kbAnimate = function(e) {
  console.log('clicking fake btn with keyboard tab + enter...');
  var code = e.which;
  // 13 = Return, 32 = Space
  if (code === 13) {
    this.click();
  }  
}

Array.from(btns).forEach(function(element) {
  element.addEventListener('click', animate);
});

Array.from(fakeBtns).forEach(function(element) {
  element.addEventListener('keydown', kbAnimate);
});
button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  outline: none;
  font-size: inherit;
}

.btn {
  all: initial;
  margin: 1em;
  display: inline-block; 
}

.btn__content {
  background: orange;
  padding: 1em;
  cursor: pointer;
  display: inline-block;
}


/* Fixing the Safari bug for `<button>`s overflow */
.btn__content {
    position: relative;
}

/* All the states on the inner element */
.btn:hover > .btn__content  {
    background: salmon;
}

.btn:active > .btn__content  {
    background: darkorange;
}

.btn:focus > .btn__content  {
    box-shadow: 0 0 2px 2px #51a7e8;
    color: lime;
}

/* Removing default outline only after we've added our custom one */
.btn:focus,
.btn__content:focus {
    outline: none;
}
<h2>Keyboard-only focus styles</h2>

<button id="btn" class="btn" type="button">
    <span class="btn__content" tabindex="-1">
        I'm a button!
    </span>
</button>

<a class="btn" href="#x">
    <span class="btn__content" tabindex="-1">
        I'm a link!
    </span>
</a>

<span class="btn" tabindex="0">
    <span class="btn__content" tabindex="-1">
        I'm a span!
    </span>
</span>

<p>Try clicking any of the the 3 focusable elements above - no focus styles will show</p>
<p>Now try tabbing + enter - behold - our interactive elements work</p>

Codepen


NB:

1) Bien que cela semble être une solution trop compliquée, pour une solution non javascript, c'est en fait assez impressionnant. Les «solutions» plus simples en CSS impliquant le style de pseudo-classe :hover et :active ne fonctionnent tout simplement pas. (à moins bien sûr que vous supposiez que l'élément interactif disparaît immédiatement au clic comme un bouton dans un mot modal)

button {
  -moz-appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  font-size: inherit;
}

.btn {
  margin: 1em;
  display: inline-block; 
  background: orange;
  padding: 1em;
  cursor: pointer;
}

.btn:hover, .btn:active {
  outline: none;
}
<h2>Remove css :focus outline only on :hover and :active states</h2>

<button class="btn" type="button">I'm a button!</button>

<a class="btn" href="#x">I'm a link!</a>

<span class="btn" tabindex="0">I'm a span!</span>

<h3>Problem: Click on an interactive element.As soon as you hover out - you get the focus styling back - because it is still focused (at least regarding the button and focusable span) </h3>

Codepen

2) Cette solution n'est pas parfaite: Firefox sur Windows obtiendra toujours des styles de focus pour les boutons au clic - mais cela semble être un bug Firefox (voir l'article)

3) Lorsque les navigateurs implémentent la : focus-ring pseudo-classe peut être une solution beaucoup plus simple à ce problème - (voir l'article) Pour ce que ça vaut, il y a un polyfill pour :focus-ring - voir cet article de Chris DeMars


Une alternative pragmatique aux styles de mise au point clavier uniquement

Il est donc étonnamment difficile de réaliser des styles de mise au point avec clavier uniquement. Une alternative / solution de contournement qui est beaucoup plus simple et qui peut à la fois répondre aux attentes du concepteur et être également accessible - consisterait à mettre l'accent sur le style comme vous le feriez pour le survol.

Codepen

Ainsi, bien que techniquement, cela n'implémente pas de styles de clavier uniquement, cela supprime essentiellement le besoin de styles de clavier uniquement.

67
Danield 10 août 2017 à 09:22

Il n'y a pas de solution claire. J'ai fait une solution Hackish: appliquez l'événement Click sur votre conteneur principal et écrivez le code ci-dessous en cliquant

    _handleMouseClick = (event) => {
        if(event.detail){
            document.activeElement.blur();
        }
    }

Lorsque vous cliquez à l'aide de la souris, vous obtiendrez event.detail = 1 sur ce clic flou cet élément afin qu'il supprime le contour et sur le clavier, nous obtenons event.detail = 0 donc dans le cas du clavier se comporte normalement

OU

Dans le fichier css

     body.disableOutline *:focus{
        outline: none !important;
    }

Dans Main js

     document.addEventListener('click', _handleMouseClick,true);
            document.addEventListener('keydown',_keydown,true);
            function _handleMouseClick(event){
                if(event.detail){
                    document.getElementsByTagName("body")[0].classList.add("disableOutline");
                }
            }
            function _keydown(e){
                document.getElementsByTagName("body")[0].classList.remove("disableOutline");
            }
0
pareshm 8 juin 2018 à 11:18

En jouant avec la solution acceptée par Danield, j'ai trouvé une manière alternative, plus simple, basée sur le concept div intérieur / extérieur.

1) Créez un élément extérieur et intérieur. Donnez l'élément externe tabindex = "0" et l'élément interne tabindex = "- 1"

<div role="button" class="outer" tabindex="0">
    <span class="inner" tabindex="-1">
        I'm a button!
    </span>
</div>

2) Dans le CSS, supprimez le contour de l'élément intérieur lorsque vous êtes focalisé:

.inner:focus{
    outline: none;
}

3) Appliquez des gestionnaires d'événements de souris ou de clic à l'élément interne. Appliquez tous les événements de focus (onfocus, onblur, onkeydown) à l'élément externe.

Par exemple:

<div role="button" class="outer" tabindex="0" onfocus="focusEventHandler()" onkeydown="handleKeyDown.bind(this, myEventHandler)">
    <div class="inner" tabindex="-1" onClick="myEventHandler()">
        I'm a button!
    </div>
</div>

** Maintenez la taille et le positionnement de sorte que l'élément intérieur chevauche complètement l'élément extérieur. Positionnez l'ensemble du "bouton" avec style sur l'élément extérieur.

Comment cela fonctionne:

Lorsque l'utilisateur clique sur le "bouton", il clique sur l'élément intérieur dont le contour du focus a été supprimé. Il n'est pas possible de cliquer sur l'élément extérieur car il est recouvert par l'élément intérieur. Lorsque l'utilisateur utilise le clavier pour tabuler le "bouton", il accède à l'élément externe (tabindex = "0" rend l'élément accessible avec 'tab') qui obtient un contour de focus, mais l'élément interne n'est pas accessible via le tab (avec tabindex = "- 1") et ne reçoit pas le contour du focus lorsque vous cliquez dessus.

4
nutsandbolts 20 nov. 2017 à 21:50
&:focus:not(:hover) { }

Cela ne fonctionnera pas dans 100% des cas, mais je pense que pour la plupart des gens, cela devrait être suffisant.

Cela empêchera l'état :focus d'être déclenché au clic car la souris doit survoler (survoler) l'élément pour le cliquer.

https://codepen.io/heyvian/pen/eopOxr

2
Vian Esterhuizen 2 avril 2019 à 21:19

Étude de cas: page de connexion Facebook

Facebook utilise actuellement un tout petit peu de Javascript sur sa page de connexion (juin 2018).

Le Javascript détecte quand l'utilisateur a cliqué sur sa souris ou utilisé son clavier, et active et désactive une classe sur le corps: <body class="using-mouse">

Les règles CSS peuvent ensuite utiliser cette classe pour afficher ou masquer le style de focus approprié sur les éléments pertinents.

Voici un exemple de code (également disponible sur CodePen). Comparez les clics et les tabulations.

// Let the document know when the mouse is being used
document.body.addEventListener('mousedown', function() {
  document.body.classList.add('using-mouse');
});

// Re-enable focus styling when Tab is pressed
document.body.addEventListener('keydown', function(event) {
  if (event.keyCode === 9) {
    document.body.classList.remove('using-mouse');
  }
});

// Alternatively, re-enable focus styling when any key is pressed
//document.body.addEventListener('keydown', function() {
//  document.body.classList.remove('using-mouse');
//});
/* The default outline styling, for greatest accessibility. */
/* You can skip this to just use the browser's defaults. */
:focus {
  outline: #08f auto 2px;
}

/* When mouse is detected, ALL focused elements have outline removed. */
body.using-mouse :focus {
  outline: none;
}
<input>
<button>Submit</button>

Notez que :focus ci-dessus est équivalent à *:focus, correspondant à tous les éléments. Si vous souhaitez uniquement supprimer le style des boutons, vous pouvez y mettre button:focus à la place.


Étude de cas: page de connexion GMail

Alternativement, à cette époque, GMail ne faisait que styliser les boutons ciblés avec une ombre plus lourde que les boutons non focalisés, que l'utilisateur soit sur la souris ou le clavier.

C'est simple à mettre en œuvre et à comprendre, et ne nécessite pas de Javascript.

:focus {
  outline: none;
  box-shadow: 0 0px 16px #0005;
}

Mais c'est un compromis. Il transmet des informations de focus qui n'intéressent pas vraiment les utilisateurs de souris, et cela pourrait être un peu trop subtil pour les utilisateurs de clavier.

Pourtant, ce compromis est probablement meilleur que l'un des deux extrêmes (un contour fort pour tous les utilisateurs, ou aucun contour du tout).


Les boutons principaux de StackOverflow utilisent une approche similaire à GMail, mais avec un aspect plus stylisé:

box-shadow: inset 0 1px 0 0 rgba(102,191,255,0.5), 0 0 0 4px rgba(0,149,255,0.15);

Personnellement, j'utiliserais une couleur plus forte (contraste plus élevé), pour l'accessibilité.

58
joeytwiddle 7 févr. 2020 à 04:41

C'est un problème que vous rencontrerez probablement beaucoup. La bonne chose à propos de ces problèmes est que si vous trouvez une fois une solution, cela ne vous dérangera plus.

La solution la plus élégante semble être la plus simple: ne supprimez pas le contour sur: focus, faites-le sur: actif à la place - après tout,: actif est la pseudo-classe dynamique qui traite explicitement des styles à appliquer lorsqu'un l'élément focalisable est cliqué ou activé d'une autre manière.

a:hover, a:active { outline: none; }

Le seul problème mineur avec cette méthode: si un utilisateur active un lien puis utilise le bouton retour du navigateur, le contour devient visible. Oh, et les anciennes versions d'Internet Explorer sont notoirement déroutées par la signification exacte de: focus,: hover et: active, donc cette méthode échoue dans IE6 et ci-dessous.

Tipp

Il existe une solution de contournement triviale pour empêcher les contours de "déborder" en ajoutant un simple overflow:hidden, qui garde le contour en échec autour de la partie cliquable de l'élément lui-même.

19
Jeremy Zahner 14 juil. 2015 à 09:29

Jusqu'à ce que :focus-visible ne soit pas présent dans tous les navigateurs à feuilles persistantes populaires, vous pouvez utiliser cette astuce simple dans la partie globale de votre CSS, sans polyfills:

@media (pointer: coarse) {
  *:focus {
    outline: none;
  }
}

Puis ajoutez des effets de mise au point comme vous le faites normalement, avec :focus.

À ce stade, vous avez probablement appris que la définition par défaut de outline: none; aux éléments focalisés est une horrible idée du point de vue de l'accessibilité. C'est certainement vrai.

Cependant, si vous définissez cette règle dans la requête média pointer: coarse, elle devient très utile, car elle ne s'applique qu'aux téléphones portables et tablettes, mais pas aux ordinateurs de bureau. C'est exactement ce que vous souhaitez réaliser.

Le seul problème auquel je peux penser est celui des utilisateurs mobiles avec des claviers, qu'ils utilisent pour parcourir le contenu, mais je ne sais pas s'il y en a beaucoup. Donc, :focus-visible sera finalement la meilleure solution, mais pour l'instant cela devrait suffire.

0
Neurotransmitter 20 janv. 2020 à 08:48

La suppression de outline est terrible pour l'accessibilité! Idéalement, la bague de mise au point n'apparaît que lorsque l'utilisateur a l'intention d'utiliser le clavier .

Réponse 2018: Utilisez : focus-visible . Il s'agit actuellement d'une proposition du W3C pour styliser le focus uniquement à l'aide du CSS. Jusqu'à ce que les principaux navigateurs le prennent en charge, vous pouvez utiliser ce polyfill robuste. Il ne nécessite pas d'ajouter des éléments supplémentaires ou de modifier le tabindex.

/* Remove outline for non-keyboard :focus */
*:focus:not(.focus-visible) {
  outline: none;
}

/* Optional: Customize .focus-visible */
.focus-visible {
  outline-color: lightgreen;
}

J'ai également écrit un post plus détaillé au cas où vous auriez besoin de plus d'informations.

22
Aaron Noel De Leon 28 mai 2018 à 17:22