web-dev-qa-db-fra.com

Activer: se concentrer uniquement sur l'utilisation du clavier (ou tabulation)

Je souhaite désactiver :focus lorsque cela n'est pas nécessaire, car je n'aime pas l'aspect de ma navigation lorsque l'accent est mis dessus. Il utilise le même style que .active et est source de confusion. Cependant, je ne veux pas m'en débarrasser pour les utilisateurs de clavier. 

Je pensais ajouter une classe enabled-focus sur le corps de l'onglet Presse puis avoir body.enabled-focus a:focus{...}, mais cela ajouterait beaucoup de CSS supplémentaire pour chaque élément ayant le focus. Supprimez ensuite cette classe du corps en premier souris.

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

26
Miro

Cet excellent article de Roman Komarov représente une solution viable pour obtenir des styles de focus uniquement du clavier pour boutons, liens et d'autres éléments de conteneur tels que comme étendues ou divs (qui sont artificiellement mises au point 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)

Alors au lieu de dire:

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

faire 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 (css doit conserver la même disposition sur l’élément externe d’origine) - de sorte que la largeur/hauteur de l’élément externe provienne de l’intérieur, etc.

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

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

4) Ajoutez le 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 interne avec tabindex="-1" - voir MDN :

Une valeur négative (généralement tabindex = "- 1" signifie que l'élément doit pouvoir être mis au point Mais ne doit pas être accessible via un clavier séquentiel Navigation ...

Ainsi, l'élément est focusable via des clics de souris ou par programme, mais d'un autre côté - il n'est pas accessible via les "onglets" du clavier. 

Ainsi, lorsque l'on clique sur l'élément interactif, l'élément inner obtient le focus. Aucun style de focus ne sera affiché car nous les avons supprimés.

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

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

D'un autre côté: quand on utilise le clavier - seul l'élément externe obtiendra le focus (rappelez-vous: l'élément interne a tabindex = "- 1" et n'est pas accessible via la navigation au clavier séquentielle) [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 démarre 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 appuyons sur enter, nous n’avons pas cassé notre élément interactif et le javascript sera exécuté.

Voici une démo pour montrer que c'est effectivement le cas. Notez que vous ne l'obtenez que gratuitement (c.-à-d. Que vous appuyez sur Entrée pour provoquer un événement clic) pour des éléments intrinsèquement interactifs tels que des boutons et des liens ... pour d'autres éléments tels que des étendues vous devez le coder 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, elle est en fait assez impressionnante. Des «solutions» css plus simples comprenant des styles :hover et :active de pseudo-classes ne fonctionnent tout simplement pas. (à moins bien sûr que vous supposiez que l'élément interactif disparaisse immédiatement après un clic, comme un bouton dans un 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 aura toujours les styles de focus pour les boutons au clic - mais cela semble être un bug de Firefox (voir l’article

3) Lorsque les navigateurs implémentent le pseudo-classe : focus-ring , il peut exister une solution beaucoup plus simple à ce problème - (voir l'article ) Pour ce que cela vaut, il existe a polyfill for :focus-ring - voir cet article de Chris DeMars


Une alternative pragmatique aux styles de focus uniquement au clavier

Il est donc étonnamment difficile d’obtenir des styles de focus uniquement au clavier. Une solution de contournement qui est beaucoup plus simple et qui peut à la fois répondre aux attentes du concepteur et aussi être accessible - serait de mettre l'accent sur le style comme vous le feriez pour un survol. 

Codepen

Donc, bien que techniquement, cela n'implémente pas les styles uniquement clavier, cela élimine essentiellement le besoin de styles uniquement clavier.

33
Danield

Supprimer 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.

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

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

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

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

12

C'est un problème que vous rencontrerez probablement beaucoup. La bonne chose à propos de tels 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 lors de l'élément activable est cliqué ou activé d'une autre manière.

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

Les seuls problèmes mineurs avec cette méthode: si un utilisateur active un lien puis utilise le bouton Précédent du navigateur, le contour devient visible. Oh, et les anciennes versions d'Internet Explorer sont notoirement confuses par la signification exacte de: focus,: hover et: active, cette méthode échoue donc dans IE6 et inférieur.

Tipp

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

11
Jeremy Zahner

En jouant avec la solution acceptée par Danield, j'ai trouvé une solution plus simple, basée sur le concept de div interne/externe.

1) Créez un élément externe et interne. Donne 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 fichier css, supprimez les contours de l’élément interne lors de la mise au point:

.inner:focus{
    outline: none;
}

3) Appliquez tous les 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>

** Conservez la taille et le positionnement de sorte que l'élément interne chevauche complètement l'élément externe. Positionnez le "bouton" entier avec style sur l'élément extérieur.

Comment ça marche:

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

3
nutsandbolts

Il n’existe pas de solution claire . J’ai fait une solution Hackish: Appliquer un événement click sur votre conteneur principal et écrire le code ci-dessous sur un clic

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

Lorsque vous cliquez avec la souris, event.detail = 1 apparaît sur cet élément afin de supprimer le contour .__ et que vous obtenez un événement event.detail = 0, vous vous comportez normalement 

OR

En fichier css

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

En 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