Eine grundlegende Übersicht zum Erstellen einer barrierefreien geteilten Schaltfläche.
In diesem Beitrag möchte ich meine Gedanken zu einer Möglichkeit zum Erstellen einer geteilten Schaltfläche teilen . Demo ansehen.
Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
Getrennte Schaltflächen sind Schaltflächen, die eine primäre Schaltfläche und eine Liste zusätzlicher Schaltflächen verbergen. Sie eignen sich gut, um eine häufig verwendete Aktion zu präsentieren und sekundäre, seltener verwendete Aktionen bis zur Verwendung zu verschachteln. Eine geteilte Schaltfläche kann entscheidend dazu beitragen, dass ein überladenes Design minimalistisch wirkt. Eine erweiterte Split-Schaltfläche kann sogar die letzte Nutzeraktion speichern und diese in die primäre Position verschieben.
Eine gängige Schaltfläche zum Splitten finden Sie in Ihrer E-Mail-Anwendung. Die primäre Aktion ist „Senden“. Sie können die Nachricht aber auch später senden oder als Entwurf speichern:
Der gemeinsame Aktionsbereich ist praktisch, da sich der Nutzer nicht umsehen muss. Er weiß, dass wichtige E-Mail-Aktionen in der geteilten Schaltfläche enthalten sind.
Teile
Sehen wir uns die wichtigsten Teile einer Split-Schaltfläche an, bevor wir die Gesamtkoordination und die endgültige Nutzererfahrung besprechen. Hier wird das Tool zur Prüfung der Barrierefreiheit von VisBug verwendet, um eine Makroansicht der Komponente zu zeigen, in der Aspekte des HTML-Codes, des Stils und der Barrierefreiheit für jeden Hauptteil angezeigt werden.
Container für Schaltfläche zum Aufteilen auf oberster Ebene
Die Komponente der obersten Ebene ist ein Inline-Flexbox mit der Klasse gui-split-button
, die die primäre Aktion und .gui-popup-button
enthält.
Die primäre Aktionsschaltfläche
Das anfangs sichtbare und fokussierbare <button>
passt in den Container. Die beiden übereinstimmenden Eckformen für Fokus, Bewegung des Mauszeigers und aktive Interaktionen sollen innerhalb von .gui-split-button
erscheinen.
Schaltfläche zum Ein-/Ausschalten des Pop-ups
Das Supportelement „Pop-up-Schaltfläche“ dient zum Aktivieren und Verweis auf die Liste der sekundären Schaltflächen. Beachten Sie, dass es sich nicht um ein <button>
handelt und dass es nicht fokussiert werden kann. Es ist jedoch der Positionierungsanker für .gui-popup
und der Host für :focus-within
, mit dem das Pop-up angezeigt wird.
Pop-up-Karte
Dies ist eine schwebende Karte, die dem Anker .gui-popup-button
untergeordnet ist, absolut positioniert ist und die Schaltflächenliste semantisch umbricht.
Die sekundären Aktionen
Eine fokussierbare <button>
mit einer etwas kleineren Schriftgröße als die primäre Aktionsschaltfläche hat ein Symbol und einen Stil, der dem der primären Schaltfläche entspricht.
Benutzerdefinierte Eigenschaften
Mit den folgenden Variablen können Sie eine Farbharmonie schaffen und an einem zentralen Ort Werte ändern, die in der gesamten Komponente verwendet werden.
@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 Farben
Markieren & Zeichnen
Das Element beginnt als <div>
mit einem benutzerdefinierten Klassennamen.
<div class="gui-split-button"></div>
Fügen Sie die primäre Schaltflä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 entscheidend, damit sie die Funktion und den Status der geteilten Schaltfläche kennen. Das title
-Attribut 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>
Bei einem einfachen Pop-up-Placement ist .gui-popup
ein untergeordnetes Element der Schaltfläche, mit der es maximiert wird. Der einzige Nachteil dieser Strategie ist, dass der .gui-split-button
-Container overflow: hidden
nicht verwenden kann, da das Pop-up dadurch nicht sichtbar ist.
Ein <ul>
mit <li><button>
-Inhalten wird von Screenreadern als „Schaltflächenliste“ angesagt, was genau der angezeigten Benutzeroberfläche entspricht.
<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>
Für mehr Flair und Farbe habe ich den sekundären Schaltflächen Symbole von https://heroicons.com hinzugefügt. Symbole sind sowohl für die primären 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
Nachdem Sie HTML und Inhalte hinzugefügt haben, können Sie mithilfe von Stilen Farbe und Layout festlegen.
Container für die geteilte Schaltfläche stylen
Ein inline-flex
-Displaytyp eignet sich gut für diese Umbruchkomponente, da sie in anderen geteilten Schaltflächen, Aktionen oder Elementen inline angezeigt werden 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;
}
Das <button>
-Styling
Schaltflächen verschleiern sehr gut, wie viel Code erforderlich ist. Möglicherweise müssen Sie die Browserstandardstile rückgängig machen oder ersetzen. Außerdem müssen Sie eine gewisse Vererbung erzwingen, Interaktionsstatus hinzufügen und sich an verschiedene Nutzereinstellungen und Eingabetypen anpassen. Schaltflächenstilen summieren sich schnell.
Diese Schaltflächen unterscheiden sich von normalen Schaltflächen, da sie denselben Hintergrund wie ein übergeordnetes Element haben. Normalerweise hat eine Schaltfläche einen eigenen Hintergrund und eine eigene Textfarbe. Diese teilen sie 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 Interaktionsstatus mit einigen CSS-Pseudoklassen hinzu und verwenden Sie passende benutzerdefinierte Eigenschaften 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);
}
}
Die primäre Schaltfläche benötigt einige spezielle Stile, 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);
}
}
Zum Schluss erhalten die Schaltfläche und das Symbol für das helle Design 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));
}
}
}
Bei einer guten Schaltfläche wurde auf die Mikrointeraktionen und kleinen Details geachtet.
Hinweis zu :focus-visible
Beachten Sie, dass in den Schaltflächenstilen :focus-visible
anstelle von :focus
verwendet wird. :focus
ist ein wichtiger Schritt zur Schaffung einer barrierefreien Benutzeroberfläche, hat aber einen Nachteil: Es wird nicht intelligent ermittelt, ob der Nutzer sie sehen muss oder nicht. Sie wird für jeden Fokus angewendet.
Im Video unten wird versucht, diese Mikrointeraktion zu zerlegen, um zu zeigen, wie :focus-visible
eine intelligente Alternative ist.
Pop-up-Schaltfläche stylen
Ein 4ch
-Flexbox zum Zentrieren eines Symbols und zum Ankern einer Liste mit Pop-up-Schaltflächen. Wie die primäre Schaltfläche ist sie transparent, bis der Mauszeiger darauf bewegt wird oder eine Interaktion damit erfolgt. Sie wird dann auf die gesamte Fläche gedehnt.
.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);
}
Mit CSS-Verschachtelung und dem funktionalen Selektor :is()
können Sie den Hover-, Fokus- und Aktivstatus überlagern:
.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 Hauptansatzpunkt für das Ein- und Ausblenden des Pop-ups. Wenn das .gui-popup-button
eines seiner untergeordneten Elemente focus
hat, legen Sie für das Symbol und das Pop-up opacity
, die 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 Sie die Ein- und Ausblendungsstile fertiggestellt haben, müssen Sie die Übergangstransformationen bedingt je nach Bewegungseinstellung des Nutzers festlegen:
.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;
}
}
}
Ein aufmerksamer Blick auf den Code zeigt, dass die Deckkraft weiterhin übergeht, wenn Nutzer weniger Bewegung bevorzugen.
Pop-up-Design
Das .gui-popup
-Element ist eine schwebende Kartenschaltflächenliste, die mit benutzerdefinierten Eigenschaften und relativen Einheiten so gestaltet ist, dass sie etwas kleiner ist, interaktiv mit der primären Schaltfläche abgeglichen wird und farblich zur Marke passt. Die Symbole haben einen geringeren Kontrast, sind dünner und der Schatten hat einen Hauch der Markenfarbe Blau. Wie bei Schaltflächen sind eine gute Benutzeroberfläche und eine gute Nutzererfahrung das Ergebnis vieler kleiner 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 erhalten Markenfarben, damit sie gut zu den jeweiligen Karten mit dunklem und hellem Design passen:
.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%;
}
}
Das Pop-up im dunklen Design hat zusätzliche Schatten für Text und Symbole sowie einen etwas intensiveren Kastenschatten:
.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 <svg>
-Symbolstile
Alle Symbole werden relativ zur Schaltfläche font-size
, in der sie verwendet werden, skaliert. Dabei wird die Einheit ch
als inline-size
verwendet. Jedem Stil werden auch einige Stile zugewiesen, um die Symbole weich und glatt zu umreißen.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
Linksläufiges Layout
Die gesamte komplexe Arbeit wird von logischen Properties erledigt.
Hier ist eine Liste der verwendeten logischen Eigenschaften:
– display: inline-flex
erstellt ein inline-Flex-Element.
– padding-block
und padding-inline
als Paar anstelle der Kurzschreibweise padding
bietet die Vorteile der Ausbuchtung der logischen Seiten.
– border-end-start-radius
und Freunde runden die Ecken je nach Dokumentausrichtung ab.
– inline-size
statt 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 je nach Skriptrichtung rechts oder links sein kann.
JavaScript
Fast das gesamte folgende JavaScript dient der Barrierefreiheit. Zwei meiner Hilfsbibliotheken werden verwendet, um die Aufgaben ein wenig zu vereinfachen. BlingBlingJS wird für kompakte DOM-Abfragen und die einfache Einrichtung von Ereignis-Listenern verwendet, während roving-ux barrierefreie Tastatur- und Gamepad-Interaktionen für das Pop-up ermöglicht.
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, sind nur noch wenige Funktionen erforderlich, um die Website zu aktualisieren.
Roving-Index
Wenn eine Tastatur oder ein Screenreader den Fokus auf die .gui-popup-button
legt, soll der Fokus auf die erste (oder zuletzt fokussierte) Schaltfläche in der .gui-popup
springen. Die Bibliothek unterstützt uns dabei mit den Parametern element
und target
.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
Das Element übergibt den Fokus jetzt an die Ziel-<button>
-Unterelemente und ermöglicht die Standardnavigation mit den Pfeiltasten, um die Optionen zu durchsuchen.
aria-expanded
ein-/ausschalten
Es ist zwar visuell erkennbar, dass ein Pop-up angezeigt und ausgeblendet wird, aber ein Screenreader benötigt mehr als visuelle Hinweise. Hier wird JavaScript verwendet, um die CSS-gestützte :focus-within
-Interaktion zu ergänzen, indem ein Attribut für Screenreader aktiviert wird.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Escape
-Schlüssel aktivieren
Der Fokus des Nutzers wurde absichtlich auf eine Falle gelenkt. Daher müssen wir eine Möglichkeit zum Verlassen bereitstellen. Am häufigsten wird die Verwendung des Escape
-Schlüssels zugelassen.
Achten Sie dazu auf Tastendrücke auf der Pop-up-Schaltfläche, da alle Tastaturereignisse auf untergeordneten Elementen an dieses übergeordnete Element weitergegeben werden.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Wenn die Pop-up-Schaltfläche eine Tastenpresse von Escape
erkennt, entfernt sie den Fokus mit blur()
.
Klicks auf Schaltflächen
Wenn der Nutzer auf die Schaltflächen klickt, sie antippt oder die Tastatur verwendet, muss die Anwendung die entsprechende Aktion ausführen. Hier wird wieder die Ereignisweitergabe verwendet, diesmal aber für den .gui-split-button
-Container, um Klicks auf Schaltflächen aus 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
Wie würden Sie das machen?
Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können. Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.