Présentation générale de la création d'un composant accessible split-button.
Dans ce message, je vais vous expliquer comment créer un bouton de division . Tester la fonctionnalité
<ph type="x-smartling-placeholder">Si vous préférez la vidéo, voici une version YouTube de cet article:
Présentation
Les boutons fractionnés sont des boutons qui dissimulent un bouton principal et une liste de boutons supplémentaires. Ils sont utiles pour exposer une action courante lors de l'imbrication d'éléments secondaires, moins fréquemment utilisé des actions jusqu'à ce que cela soit nécessaire. Un bouton de division peut être crucial pour aider une conception chargée sont minimes. Un bouton de répartition avancée peut même se souvenir de la dernière action de l'utilisateur et le promouvoir en position principale.
Vous trouverez un bouton de répartition courant dans votre application de messagerie. L'action principale est envoyé, mais vous pourrez peut-être l'envoyer plus tard ou enregistrer un brouillon à la place:
La zone d'action partagée est agréable, car l'utilisateur n'a pas besoin de regarder autour de lui. Ils sachez que le bouton "Split" (Diviser) contient les actions essentielles sur l'e-mail.
Pièces
Analysons les éléments essentiels d'un bouton fractionné l'orchestration globale et l'expérience utilisateur finale. Accessibilité de VisBug L'outil d'inspection permet d'afficher une vue macro du composant, du code HTML, du style et de l'accessibilité pour chaque aspect important.
Conteneur du bouton de fractionnement de niveau supérieur
Le composant de niveau supérieur est un Flexbox intégré, avec une classe de
gui-split-button
, contenant l'action principale
et .gui-popup-button
.
Le bouton d'action principal
Le <button>
initialement visible et sélectionnable tient dans le conteneur avec
deux formes d'angle
correspondantes pour
ciblage,
pointer et
des interactions actives pour
apparaissent dans .gui-split-button
.
Bouton d'activation de la fenêtre pop-up
Le "bouton pop-up" sert à activer et à faire allusion à la liste
boutons secondaires. Notez qu'il ne s'agit pas d'un élément <button>
et qu'il n'est pas sélectionnable. Toutefois,
Il s'agit de l'ancre de positionnement pour .gui-popup
et de l'hôte pour :focus-within
utilisé.
pour présenter le pop-up.
La fiche pop-up
Il s'agit d'une carte flottante enfant à son ancre
.gui-popup-button
, position absolue et
en encapsulant sémantiquement la liste de boutons.
Les actions secondaires
Un élément <button>
sélectionnable avec une taille de police légèrement inférieure à celle de l'élément principal
bouton d'action se compose d'une icône et d'un bouton sans frais
au bouton principal.
Propriétés personnalisées
Les variables suivantes contribuent à créer une harmonie des couleurs et un emplacement central pour modifier les valeurs utilisées dans le composant.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);
.gui-split-button {
--theme: hsl(220 75% 50%);
--theme-hover: hsl(220 75% 45%);
--theme-active: hsl(220 75% 40%);
--theme-text: hsl(220 75% 25%);
--theme-border: hsl(220 50% 75%);
--ontheme: hsl(220 90% 98%);
--popupbg: hsl(220 0% 100%);
--border: 1px solid var(--theme-border);
--radius: 6px;
--in-speed: 50ms;
--out-speed: 300ms;
@media (--dark) {
--theme: hsl(220 50% 60%);
--theme-hover: hsl(220 50% 65%);
--theme-active: hsl(220 75% 70%);
--theme-text: hsl(220 10% 85%);
--theme-border: hsl(220 20% 70%);
--ontheme: hsl(220 90% 5%);
--popupbg: hsl(220 10% 30%);
}
}
Mises en page et couleur
Majoration
L'élément commence par une <div>
avec un nom de classe personnalisé.
<div class="gui-split-button"></div>
Ajoutez le bouton principal et les éléments .gui-popup-button
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>
Notez les attributs ARIA aria-haspopup
et aria-expanded
. Ces indices sont
essentiel pour que les lecteurs d'écran connaissent la fonctionnalité et l'état du fractionnement
de l'expérience utilisateur. L'attribut title
est utile pour tout le monde.
Ajoutez une icône <svg>
et l'élément conteneur .gui-popup
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup"></ul>
</span>
</div>
Pour placer directement les pop-up, .gui-popup
est un enfant du bouton qui
la développe. Le seul inconvénient de cette stratégie est .gui-split-button
.
Le conteneur ne peut pas utiliser overflow: hidden
, car il empêchera le pop-up d'être
visuellement présent.
Un élément <ul>
rempli avec du contenu <li><button>
s'annoncera en tant que "bouton"
liste" aux lecteurs d'écran, c'est-à-dire
exactement l'interface présentée.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li>
<button>Schedule for later</button>
</li>
<li>
<button>Delete</button>
</li>
<li>
<button>Save draft</button>
</li>
</ul>
</span>
</div>
Pour plus de style et pour m'amuser avec la couleur, j'ai ajouté des icônes aux boutons secondaires sur https://heroicons.com. Les icônes sont facultatives pour les deux les boutons principal et secondaire.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Schedule for later
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Save draft
</button></li>
</ul>
</span>
</div>
Styles
Une fois le code HTML et le contenu en place, les styles sont prêts à fournir de la couleur et une mise en page.
Appliquer un style au conteneur du bouton de fractionnement
Un type d'affichage inline-flex
fonctionne bien pour ce composant d'encapsulation, car il
doit s'aligner avec les autres actions, éléments ou boutons fractionnés.
.gui-split-button {
display: inline-flex;
border-radius: var(--radius);
background: var(--theme);
color: var(--ontheme);
fill: var(--ontheme);
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
Style <button>
Les boutons sont très efficaces pour masquer la quantité de code nécessaire. Vous devrez peut-être annuler ou remplacer les styles par défaut du navigateur, mais vous devrez aussi en appliquer l’héritage, ajouter des états d’interaction et s’adapter à différentes préférences et types d'entrée. Les styles de boutons s'additionnent rapidement.
Ces boutons sont différents des boutons standards, car ils partagent un arrière-plan avec un élément parent. Habituellement, un bouton possède son arrière-plan et sa couleur de texte. En revanche, celles-ci le partagent et n'appliquent que leur propre arrière-plan lors de l'interaction.
.gui-split-button button {
cursor: pointer;
appearance: none;
background: none;
border: none;
display: inline-flex;
align-items: center;
gap: 1ch;
white-space: nowrap;
font-family: inherit;
font-size: inherit;
font-weight: 500;
padding-block: 1.25ch;
padding-inline: 2.5ch;
color: var(--ontheme);
outline-color: var(--theme);
outline-offset: -5px;
}
Ajouter des états d'interaction avec quelques CSS pseudo-classes et utilisation de la mise en correspondance personnalisées pour l'état:
.gui-split-button button {
…
&:is(:hover, :focus-visible) {
background: var(--theme-hover);
color: var(--ontheme);
& > svg {
stroke: currentColor;
fill: none;
}
}
&:active {
background: var(--theme-active);
}
}
Le bouton principal a besoin de quelques styles spéciaux pour compléter l'effet de conception:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
Enfin, pour apporter une touche d'élégance, le bouton et l'icône du thème clair shadow:
.gui-split-button {
@media (--light) {
& > button,
& button:is(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--theme-active);
}
& > .gui-popup-button > svg,
& button:is(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--theme-active));
}
}
}
Un bon bouton a prêté attention aux micro-interactions et aux minuscules détails.
Remarque concernant :focus-visible
Notez que les styles de bouton utilisent :focus-visible
au lieu de :focus
. :focus
est une touche cruciale pour rendre
une interface utilisateur accessible, mais elle a une
une chute: il n'est pas judicieux de savoir
si l'utilisateur a besoin ou non de la voir ou
non, il s'appliquera à tous les ciblages.
La vidéo ci-dessous tente de décomposer cette microinteraction pour vous montrer
:focus-visible
est une alternative intelligente.
Appliquer un style au bouton pop-up
Flexbox 4ch
permettant de centrer une icône et d'ancrer une liste de boutons pop-up. J'aime
le bouton principal, il reste transparent jusqu'à ce qu'il soit passé la souris ou qu'il n'interagisse pas
et étirée pour remplir l'espace.
.gui-popup-button {
inline-size: 4ch;
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-inline-start: var(--border);
border-start-end-radius: var(--radius);
border-end-end-radius: var(--radius);
}
Superposez des éléments à l'état actif, actif ou au passage de la souris avec CSS.
L'imbrication
Sélecteur de fonctionnalité :is()
:
.gui-popup-button {
…
&:is(:hover,:focus-within) {
background: var(--theme-hover);
}
/* fixes iOS trying to be helpful */
&:focus {
outline: none;
}
&:active {
background: var(--theme-active);
}
}
Ces styles constituent l'accroche principale pour afficher et masquer le pop-up. Lorsque
.gui-popup-button
comporte focus
sur n'importe lequel de ses enfants, définissez opacity
, position
et pointer-events
, sur l'icône et le pop-up.
.gui-popup-button {
…
&:focus-within {
& > svg {
transition-duration: var(--in-speed);
transform: rotateZ(.5turn);
}
& > .gui-popup {
transition-duration: var(--in-speed);
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
}
}
Une fois les étapes d'entrée et de sortie terminées, la dernière partie consiste à de manière conditionnelle transformations de transition en fonction des préférences de mouvement de l'utilisateur:
.gui-popup-button {
…
@media (--motionOK) {
& > svg {
transition: transform var(--out-speed) ease;
}
& > .gui-popup {
transform: translateY(5px);
transition:
opacity var(--out-speed) ease,
transform var(--out-speed) ease;
}
}
}
Un œil attentif sur le code remarquera que l'opacité est encore en transition pour les utilisateurs. qui préfèrent les mouvements réduits.
Appliquer un style au pop-up
L'élément .gui-popup
est une liste de boutons de carte flottante utilisant des propriétés personnalisées.
et les unités relatives sont légèrement plus petites et mises en correspondance de manière interactive avec
le bouton et sur la marque
avec son utilisation de la couleur. Remarquez que les icônes
ont moins de contraste,
sont plus fines, et l'ombre présente un soupçon de bleu. Comme pour les boutons,
une interface utilisateur et une expérience utilisateur solides
découle de l'empilement de ces petits détails.
.gui-popup {
--shadow: 220 70% 15%;
--shadow-strength: 1%;
opacity: 0;
pointer-events: none;
position: absolute;
bottom: 80%;
left: -1.5ch;
list-style-type: none;
background: var(--popupbg);
color: var(--theme-text);
padding-inline: 0;
padding-block: .5ch;
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
font-size: .9em;
transition: opacity var(--out-speed) ease;
box-shadow:
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
;
}
Les icônes et les boutons se voient attribuer les couleurs de la marque pour un style harmonieux et thème clair:
.gui-popup {
…
& svg {
fill: var(--popupbg);
stroke: var(--theme);
@media (prefers-color-scheme: dark) {
stroke: var(--theme-border);
}
}
& button {
color: var(--theme-text);
width: 100%;
}
}
Le pop-up du thème sombre propose des ombres de texte et d'icône, ainsi que des éléments intense box shadow:
.gui-popup {
…
@media (--dark) {
--shadow-strength: 5%;
--shadow: 220 3% 2%;
& button:not(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--ontheme);
}
& button:not(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--ontheme));
}
}
}
Styles d'icônes génériques <svg>
Toutes les icônes ont une taille relativement proche du bouton font-size
dans lequel elles sont utilisées
en utilisant l'unité ch
inline-size
Chacun dispose également de styles pour
aider à définir les icônes douces et
fluide.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
Mise en page de droite à gauche
Les propriétés logiques effectuent les tâches les plus complexes.
Voici la liste des propriétés logiques utilisées:
- display: inline-flex
crée un élément flexible intégré.
- padding-block
et padding-inline
combinés, au lieu de padding
obtenir les avantages du remplissage
des côtés logiques.
- border-end-start-radius
et
amis va
et arrondir les angles selon l'orientation du document.
- inline-size
au lieu de width
garantit que la taille n'est pas liée à des dimensions physiques.
- border-inline-start
ajoute une bordure au début, qui peut être à droite ou à gauche selon l'orientation du script.
JavaScript
La quasi-totalité du code JavaScript suivant vise à améliorer l'accessibilité. Deux de mes les bibliothèques d'aide sont utilisées pour faciliter un peu les tâches. BlingBlingJS est utilisé pour les scripts concis des requêtes DOM et une configuration aisée de l'écouteur d'événements, roving-ux aide à faciliter l'accès les interactions avec le clavier et la manette de jeu pour le pop-up.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
Avec les bibliothèques ci-dessus importées et les éléments sélectionnés et enregistrés dans et il vous reste quelques fonctions à compléter.
Indice de rotation
Lorsqu'un clavier ou un lecteur d'écran effectue la mise au point sur le .gui-popup-button
, nous voulons
déplacer le curseur vers le premier bouton (ou le plus récent) de la
.gui-popup
La bibliothèque nous aide à le faire avec element
et target
.
paramètres.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
L'élément passe maintenant le curseur aux enfants <button>
cibles et active
Flèche standard pour parcourir les options
Activation/Désactivation de aria-expanded
...
Bien qu'il soit visuellement évident qu'un pop-up s'affiche et se masque, un lecteur d'écran a besoin de plus que de repères visuels. JavaScript est utilisé ici pour compléter l'interaction :focus-within
pilotée par CSS en activant un attribut approprié pour le lecteur d'écran.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Activer la clé Escape
L'attention de l'utilisateur a été délibérément
pris dans un piège, ce qui signifie que nous devons
proposent un moyen de partir. La méthode la plus courante consiste à autoriser l'utilisation de la clé Escape
.
Pour ce faire, soyez attentif aux appuis sur le bouton pop-up, car les événements de clavier
les enfants remonteront vers ce parent.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Si le bouton pop-up détecte des pressions sur la touche Escape
, il n'est plus sélectionné.
par
blur()
Clics sur le bouton de répartition
Enfin, si l'utilisateur clique, appuie ou utilise le clavier,
l'application doit effectuer l'action appropriée. L'ébullition de l'événement est utilisée
ici, mais cette fois sur le conteneur .gui-split-button
, pour intercepter
clics à partir d'un pop-up enfant ou de l'action principale.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
Conclusion
Maintenant que vous savez comment j'ai fait, comment feriez-vous ? 😃
Diversifiez nos approches et découvrons toutes les manières de créer des applications sur le Web. Créer une démonstration, me envoyer des tweets et je l'ajouterai à la section des remix de la communauté ci-dessous.