Ein grundlegender Überblick darüber, wie du eine barrierefreie Komponente für eine geteilte Schaltfläche erstellst.
In diesem Beitrag möchte ich euch zeigen, wie ihr eine Schaltfläche zum Teilen erstellen könnt . Demo ansehen.
Falls du lieber ein Video hast, findest du hier eine YouTube-Version dieses Beitrags:
Überblick
Teilen-Schaltflächen sind Schaltflächen, die eine Hauptschaltfläche und eine Liste zusätzlicher Schaltflächen verbergen. Sie sind nützlich, um eine gängige Aktion sichtbar zu machen und gleichzeitig sekundäre, weniger häufig verwendete Aktionen zu verschachteln, bis sie benötigt werden. Eine Aufteilungsschaltfläche kann entscheidend sein, damit sich ein unübersichtliches Design minimal anfühlt. Mit der erweiterten Aufteilungsschaltfläche können Sie sich sogar die letzte Nutzeraktion merken und diese an die primäre Position verschieben.
Eine gemeinsame Schaltfläche zum Aufteilen finden Sie in Ihrer E-Mail-Anwendung. Die primäre Aktion ist „Senden“. Sie können die Aktion aber auch später senden oder einen Entwurf speichern:
Der Bereich mit den freigegebenen Aktionen ist gut, da sich der Nutzer nicht umsehen muss. Er weiß, dass die Schaltfläche zum Aufteilen wichtige E-Mail-Aktionen enthält.
Teile
Sehen wir uns die wesentlichen Bestandteile einer Schaltfläche zum Aufteilen genauer an, bevor wir auf ihre Gesamtorchestrierung und die User Experience eingehen. Hier wird das Tool für die Prüfung der Barrierefreiheit von VisBug verwendet, um eine Makroansicht der Komponente anzuzeigen, in der Aspekte des HTML-Codes, der Stil und die Barrierefreiheit für jeden Hauptteil angezeigt werden.
Container für geteilte Schaltflächen der obersten Ebene
Die Komponente auf der obersten Ebene ist eine Inline-Flexbox mit einer Klasse von gui-split-button
, die die primäre Aktion und die .gui-popup-button
enthält.
Die primäre Aktionsschaltfläche
Die anfänglich sichtbare und fokussierbare <button>
passt in den Container mit zwei übereinstimmenden Eckenformen für Fokuss, hover und aktive Interaktionen, die in .gui-split-button
enthalten sind.
Die Ein/Aus-Schaltfläche für das Pop-up
Das Supportelement „Pop-up-Schaltfläche“ dient dazu, die Liste der Sekundärschaltflächen zu aktivieren und darauf hinzuweisen. Sie sehen, dass es sich nicht um ein <button>
handelt und es nicht fokussierbar ist. Er ist jedoch der Positionsanker für .gui-popup
und den Host für :focus-within
, der zum Präsentieren des Pop-ups verwendet wird.
Pop-up-Karte
Dies ist eine unverankerte Karte, die dem Anker-.gui-popup-button
untergeordnet ist und absolut und semantisch die Schaltflächenliste umschließt.
Die sekundäre(n) Aktion(en)
Eine fokussierbare <button>
mit einer etwas kleineren Schriftgröße als die primäre Aktionsschaltfläche enthält ein Symbol und einen ergänzenden Stil zur primären Schaltfläche.
Benutzerdefinierte Eigenschaften
Die folgenden Variablen helfen beim Erstellen der Farbharmonie und an einem zentralen Ort, an dem die in der Komponente verwendeten Werte geändert werden können.
@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%);
}
}
Layouts und Farbe
Markup
Das Element beginnt als <div>
mit einem benutzerdefinierten Klassennamen.
<div class="gui-split-button"></div>
Füge die Hauptschaltfläche und die .gui-popup-button
-Elemente hinzu.
<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>
Beachten Sie die aria-Attribute aria-haspopup
und aria-expanded
. Diese Hinweise sind für Screenreader von entscheidender Bedeutung, um sich über die Möglichkeiten und den Status der Aufteilungsschaltflächen zu informieren. Das Attribut title
ist für alle hilfreich.
Fügen Sie ein <svg>
-Symbol und das Containerelement .gui-popup
hinzu.
<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>
Für eine einfache Pop-up-Platzierung ist .gui-popup
der Schaltfläche zum Maximieren untergeordnet. Der einzige Haken bei dieser Strategie ist, dass der .gui-split-button
-Container overflow: hidden
nicht verwenden kann, da das Pop-up dadurch nicht mehr visuell angezeigt wird.
Ein <ul>
, das mit <li><button>
-Inhalten gefüllt ist, wird Screenreadern als „Schaltflächenliste“ angezeigt. Das entspricht genau der Benutzeroberfläche, die präsentiert wird.
<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>
Aus Gründen der Erinnerung und mit Farbe habe ich den sekundären Schaltflächen von https://heroicons.com Symbole hinzugefügt. Symbole sind sowohl für die primäre als auch für die sekundären Schaltflächen optional.
<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>
Stile
Wenn HTML und Inhalte vorhanden sind, können die Stile Farben und Layout bereitstellen.
Container für die Schaltfläche „Aufteilen“ gestalten
Der Anzeigetyp inline-flex
eignet sich gut für diese Wrapping-Komponente, da er in die anderen Teilen-Schaltflächen, Aktionen oder Elemente passen sollte.
.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;
}
Der <button>
-Stil
Schaltflächen vermitteln sehr gut einen Eindruck davon, wie viel Code erforderlich ist. Unter Umständen müssen Sie die Standardstile des Browsers rückgängig machen oder ersetzen, aber auch eine gewisse Übernahme erzwingen, Interaktionsstatus hinzufügen und die Stile an verschiedene Nutzereinstellungen und Eingabetypen anpassen. Schaltflächenstile wachsen schnell.
Diese Schaltflächen unterscheiden sich von regulären Schaltflächen, da sie denselben Hintergrund wie ein übergeordnetes Element haben. Normalerweise wird die Hintergrundfarbe und die Textfarbe einer Schaltfläche zugewiesen. Diese teilen es jedoch und wenden nur ihren eigenen Hintergrund auf die Interaktion an.
.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;
}
Fügen Sie mit einigen CSS-Pseudoklassen Interaktionsstatus hinzu und verwenden Sie übereinstimmende benutzerdefinierte Attribute für den Status:
.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);
}
}
Für die primäre Schaltfläche sind einige spezielle Stile erforderlich, um den Designeffekt zu vervollständigen:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
Die Schaltfläche und das Symbol des hellen Designs erhalten einen Schatten:
.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));
}
}
}
Eine tolle Schaltfläche hat auf die Mikrointeraktionen und die kleinen Details geachtet.
Hinweis zu :focus-visible
Für die Schaltflächenstile wird :focus-visible
anstelle von :focus
verwendet. :focus
ist ein entscheidender Aspekt beim Erstellen einer barrierefreien Benutzeroberfläche, hat aber einen Nachteil: Es ist nicht intelligent darin, ob der Nutzer sie sehen muss oder nicht, sie gilt für jeden Fokus.
Im folgenden Video wird versucht, diese Mikrointeraktion aufzuschlüsseln, um zu zeigen, dass :focus-visible
eine intelligente Alternative ist.
Pop-up-Schaltfläche gestalten
Eine 4ch
-Flexbox, um ein Symbol zu zentrieren und eine Pop-up-Schaltflächenliste zu verankern. Wie die primäre Schaltfläche ist sie transparent, bis der Mauszeiger darauf bewegt wird oder mit ihr interagiert. Sie wird gestreckt, damit sie gefüllt wird.
.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);
}
Über die CSS-Verschachtelung und den Funktionsselektor :is()
legen Sie Ebenen für den Hover-, Fokus- und aktiven Status fest:
.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);
}
}
Diese Stile sind der Hauptmechanismus zum Ein- und Ausblenden des Pop-ups. Wenn .gui-popup-button
für eines der untergeordneten Elemente focus
hat, legen Sie für das Symbol und das Pop-up opacity
, Position und pointer-events
fest.
.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;
}
}
}
Nachdem die In- und Out-Stile abgeschlossen sind, besteht der letzte Schritt darin, je nach Bewegungspräferenz des Nutzers bedingte Übergangstransformationen zu verwenden:
.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;
}
}
}
Wenn Sie den Code genau beobachten, stellen Sie fest, dass für Nutzer, die Bewegungen mit reduzierter Bewegung bevorzugen, die Deckkraft weiterhin umgestellt wird.
Pop-up gestalten
Das .gui-popup
-Element ist eine unverankerte Schaltflächenliste einer Karte mit benutzerdefinierten Eigenschaften und relativen Einheiten, die geringfügig kleiner sind, interaktiv an die primäre Schaltfläche angepasst und durch Farbgebung auf die Marke abgestimmt werden. Die Symbole haben weniger Kontrast,
sind dünner und der Schatten hat einen Hauch von Markenblau. Wie bei Schaltflächen ist eine gute
UI und UX das Ergebnis dieser kleinen Details.
.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%))
;
}
Die Symbole und Schaltflächen werden mit Markenfarben versehen, um sie auf jeder Karte mit dunklen und hellen Designs ansprechend zu gestalten:
.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%;
}
}
Im Pop-up-Fenster mit dem dunklen Design wurden Text- und Symbolschatten sowie ein etwas intensiverer Feldschatten hinzugefügt:
.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));
}
}
}
Allgemeine Symbolstile für <svg>
Alle Symbole haben eine ähnliche Größe wie die Schaltfläche font-size
, in der sie verwendet werden. Dabei wird die Einheit ch
als inline-size
verwendet. Jedem erhält auch einige Stile, die dazu beitragen, die Symbole „weich“ und „glatt“ zu gestalten.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
Linksläufiges Layout
Logische Attribute erledigen die komplexe Arbeit.
Hier die Liste der verwendeten logischen Attribute:
– display: inline-flex
erstellt ein Inline-Flex-Element.
– padding-block
und padding-inline
als Paar statt padding
– das hat den Vorteil, dass die logischen Seiten aufgefüllt werden.
– border-end-start-radius
und Freunde runden Ecken je nach Richtung des Dokuments ab.
– inline-size
anstelle von width
sorgt dafür, dass die Größe nicht an physische Abmessungen gebunden ist.
– border-inline-start
fügt dem Anfang einen Rahmen hinzu, der sich je nach Skriptrichtung auf der rechten oder linken Seite befinden kann.
JavaScript
Nahezu alle der folgenden JavaScript-Elemente dienen der Verbesserung der Barrierefreiheit. Zwei meiner Hilfsbibliotheken werden verwendet, um die Aufgaben ein wenig zu vereinfachen. BlingBlingJS wird für prägnante DOM-Abfragen und eine einfache Einrichtung des Event-Listeners verwendet. roving-ux erleichtert barrierefreie Tastatur- und Gamepad-Interaktionen für das Pop-up.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
Nachdem die oben genannten Bibliotheken importiert und die Elemente ausgewählt und in Variablen gespeichert wurden, ist das Upgrade der Umgebung nur noch wenige Schritte abgeschlossen.
Roving-Index
Wenn eine Tastatur oder ein Screenreader den .gui-popup-button
fokussiert, soll der Fokus zur ersten (oder zuletzt fokussierten) Schaltfläche im .gui-popup
gelenkt werden. Die Bibliothek verwendet dazu die Parameter element
und target
.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
Das Element übergibt jetzt den Fokus an die untergeordneten <button>
-Ziele und aktiviert die Standardnavigation mit den Pfeiltasten zum Durchsuchen von Optionen.
aria-expanded
wird umgeschaltet
Obwohl es visuell deutlich ist, dass ein Pop-up ein- und ausgeblendet wird, benötigt ein Screenreader mehr als visuelle Hinweise. JavaScript wird hier verwendet, um die CSS-gesteuerte :focus-within
-Interaktion zu ergänzen, indem ein Screenreader-Attribut umgeschaltet wird.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Escape
-Schlüssel wird aktiviert
Der Fokus des Nutzers wurde absichtlich auf eine Falle gelenkt, was bedeutet, dass wir eine Möglichkeit zum Verlassen anbieten müssen. Die gängigste Methode besteht darin, die Verwendung des Schlüssels Escape
zuzulassen.
Achten Sie dazu auf Tastendrücke auf die Pop-up-Schaltfläche, da Tastaturereignisse für untergeordnete Elemente in diesem übergeordneten Element vorkommen.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Wenn die Pop-up-Schaltfläche das Drücken der Escape
-Taste erkennt, wird der Fokus mit blur()
von sich selbst entfernt.
Klicks auf die Schaltfläche „Aufteilen“
Wenn der Nutzer schließlich auf die Schaltflächen klickt, tippt oder mit der Tastatur interagiert, muss die Anwendung die entsprechende Aktion ausführen. Hier wird noch einmal Ereignis-Bubbling verwendet, jedoch im .gui-split-button
-Container, um Klicks auf Schaltflächen von einem untergeordneten Pop-up oder der primären Aktion zu erfassen.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
Fazit
Jetzt weißt du, wie ich es gemacht habe. Wie würdest du es erreichen? 🙂
Diversifizieren wir unsere Ansätze und lernen Sie alle Möglichkeiten kennen, wie wir das Web nutzen können. Erstelle eine Demo und twittere mich über Links, und ich füge sie unten zum Abschnitt über Community-Remixe hinzu.