Eine grundlegende Übersicht darüber, wie Sie eine zugängliche Komponente mit geteilter Schaltfläche erstellen.
In diesem Beitrag möchte ich meine Überlegungen zum Erstellen einer geteilten Schaltfläche teilen . Demo ansehen.
Wenn du lieber ein Video ansehen möchtest, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
Aufgeteilte Schaltflächen sind Schaltflächen, die eine primäre Schaltfläche und eine Liste mit zusätzlichen Schaltflächen verbergen. Sie sind nützlich, um eine häufige Aktion zu präsentieren und gleichzeitig sekundäre, seltener verwendete Aktionen zu verschachteln, bis sie benötigt werden. Eine geteilte Schaltfläche kann dazu beitragen, dass ein überladenes Design minimalistisch wirkt. Bei einem erweiterten Split-Button wird möglicherweise sogar die letzte Nutzeraktion gespeichert und an die primäre Position verschoben.
Eine gängige Schaltfläche mit Drop-down-Menü finden Sie in Ihrer E‑Mail-Anwendung. Die primäre Aktion ist „Senden“. Möglicherweise können Sie die Nachricht aber auch später senden oder einen Entwurf speichern:
Der Bereich für freigegebene Aktionen ist praktisch, da der Nutzer nicht suchen muss. Sie wissen, dass wichtige E‑Mail-Aktionen in der geteilten Schaltfläche enthalten sind.
Teile
Sehen wir uns die wesentlichen Bestandteile einer geteilten Schaltfläche an, bevor wir auf die Orchestrierung und die endgültige Nutzererfahrung eingehen. Das Tool zur Überprüfung der Barrierefreiheit von VisBug wird hier verwendet, um eine Makroansicht der Komponente zu erstellen und Aspekte des HTML-Codes, des Stils und der Barrierefreiheit für jeden wichtigen Teil zu präsentieren.
Container für die Schaltfläche „Aufteilen“ der obersten Ebene
Die Komponente der obersten Ebene ist eine Inline-Flexbox mit der Klasse gui-split-button
, die die primäre Aktion und .gui-popup-button
enthält.
Primäre Aktionsschaltfläche
Das anfänglich sichtbare und fokussierbare <button>
passt in den Container und hat zwei passende Ecken für die Interaktionen focus, hover und active, sodass es innerhalb von .gui-split-button
angezeigt wird.
Ein-/Aus-Schaltfläche für Pop-ups
Das Unterstützungselement „Pop-up-Schaltfläche“ dient zum Aktivieren und Andeuten der Liste der sekundären Schaltflächen. Beachten Sie, dass es sich nicht um ein <button>
handelt und es nicht fokussierbar ist. Es ist jedoch der Positionierungsanker für .gui-popup
und der Host für :focus-within
, der zum Präsentieren des Pop-ups verwendet wird.
Die Pop-up-Karte
Dies ist ein untergeordnetes Element einer schwebenden Karte, die absolut positioniert ist und die Schaltflächenliste semantisch umschließt..gui-popup-button
Die sekundären Aktionen
Ein fokussierbares <button>
mit einer etwas kleineren Schriftgröße als die primäre Aktionsschaltfläche hat ein Symbol und einen ergänzenden Stil zur primären Schaltfläche.
Benutzerdefinierte Eigenschaften
Die folgenden Variablen helfen dabei, Farbharmonie zu schaffen und bieten einen zentralen Ort, um Werte zu ä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 wichtig, damit Screenreader die Funktion und den Status der geteilten Schaltfläche erkennen können. Das Attribut title
ist für alle hilfreich.
Fügen Sie ein <svg>
-Symbol und das .gui-popup
-Containerelement 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 einer einfachen Pop-up-Platzierung ist .gui-popup
ein untergeordnetes Element der Schaltfläche, mit der das Pop-up maximiert wird. Der einzige Haken bei dieser Strategie ist, dass im .gui-split-button
-Container kein overflow: hidden
verwendet werden kann, da das Pop-up sonst nicht sichtbar ist.
Ein <ul>
mit <li><button>
-Inhalten wird von Screenreadern als „Schaltflächenliste“ angesagt, was genau der präsentierten 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>
Um das Design aufzulockern und mit Farben zu spielen, 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 HTML und Inhalte vorhanden sind, können Sie mit Stilen Farbe und Layout festlegen.
Container für geteilte Schaltfläche gestalten
Der inline-flex
-Anzeigetyp eignet sich gut für diese Wrapping-Komponente, da sie in andere geteilte Schaltflächen, Aktionen oder Elemente eingefügt 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;
}
Die <button>
-Formatierung
Schaltflächen sind sehr gut darin, zu verschleiern, wie viel Code erforderlich ist. Möglicherweise müssen Sie die Standardstile des Browsers rückgängig machen oder ersetzen. Außerdem müssen Sie die Vererbung erzwingen, Interaktionsstatus hinzufügen und sich an verschiedene Nutzereinstellungen und Eingabetypen anpassen. Die Anzahl der Schaltflächenstile kann schnell ansteigen.
Diese Schaltflächen unterscheiden sich von regulären Schaltflächen, da sie einen Hintergrund mit einem übergeordneten Element gemeinsam nutzen. Normalerweise hat eine Schaltfläche eine eigene Hintergrund- und Textfarbe. 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 Interaktionsstatus mit einigen CSS-Pseudoklassen und passenden benutzerdefinierten Eigenschaften für den Status hinzu:
.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 Formatierungen erforderlich, um den gewünschten Designeffekt zu erzielen:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
Außerdem haben die Schaltfläche und das Symbol für das helle Design einen Schatten erhalten:
.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 wurden die Mikrointeraktionen und kleinen Details berücksichtigt.
Hinweis zu :focus-visible
Beachten Sie, dass für die Schaltflächenstile :focus-visible
anstelle von :focus
verwendet wird. :focus
ist ein wichtiger Schritt zur Erstellung einer barrierefreien Benutzeroberfläche, hat aber einen Nachteil: Es wird nicht berücksichtigt, ob der Nutzer die Informationen sehen muss oder nicht. Die Funktion wird bei jedem Fokus angewendet.
Im folgenden Video wird versucht, diese Mikrointeraktion aufzuschlüsseln, um zu zeigen, wie :focus-visible
eine intelligente Alternative ist.
Pop-up-Schaltfläche gestalten
Eine 4ch
-Flexbox zum Zentrieren eines Symbols und zum Verankern einer Pop-up-Schaltflächenliste. Wie die primäre Schaltfläche ist sie transparent, bis der Mauszeiger darauf bewegt wird oder eine Interaktion erfolgt. Sie wird so gestreckt, dass sie den gesamten Bereich ausfüllt.
.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-Nesting und dem funktionalen Selektor :is()
können Sie Ebenen in den Status „hover“, „focus“ und „active“ einfügen:
.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 primäre Hook zum Einblenden und Ausblenden des Pop-ups. Wenn das .gui-popup-button
-Element focus
für eines seiner untergeordneten Elemente enthält, legen Sie opacity
, position und pointer-events
für das Symbol und das Pop-up 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 Ein- und Ausblende-Stile fertig sind, müssen Sie die Transformationen je nach Bewegungseinstellung des Nutzers bedingt überblenden:
.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;
}
}
}
Bei genauerer Betrachtung des Codes fällt auf, dass die Deckkraft weiterhin für Nutzer mit reduzierter Bewegung überblendet wird.
Pop-up gestalten
Das .gui-popup
-Element ist eine Liste mit schwebenden Karten-Schaltflächen, die benutzerdefinierte Eigenschaften und relative Einheiten verwendet, um dezent kleiner zu sein. Es ist interaktiv auf die primäre Schaltfläche abgestimmt und entspricht der Marke durch die Verwendung von Farben. Die Symbole haben weniger Kontrast, sind dünner und der Schatten hat einen Hauch von Markenblau. Wie bei Schaltflächen ist ein starkes 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 erhalten Markenfarben, damit sie in jeder Karte mit dunklem und hellem Design gut aussehen:
.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 für das dunkle Design enthält zusätzlichen Text und einen zusätzlichen Symbolschatten sowie einen etwas intensiveren Schlagschatten:
.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
skaliert, in der sie verwendet werden. Dazu wird die Einheit ch
als inline-size
verwendet. Außerdem werden für jedes Symbol einige Stile angegeben, um die Konturen 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 ist die Liste der verwendeten logischen Eigenschaften:
- display: inline-flex
erstellt ein Inline-Flex-Element.
– padding-block
und padding-inline
als Paar anstelle der Kurzform padding
verwenden, um die Vorteile der Auffüllung der logischen Seiten zu nutzen.
– border-end-start-radius
und Freunde runden Ecken basierend auf der Dokumentrichtung.
– inline-size
statt width
sorgt dafür, dass die Größe nicht an physische Abmessungen gebunden ist.
– border-inline-start
fügt am Anfang einen Rahmen hinzu, der je nach Schreibrichtung rechts oder links sein kann.
JavaScript
Fast das gesamte folgende JavaScript dient dazu, die Barrierefreiheit zu verbessern. Zwei meiner Hilfsbibliotheken werden verwendet, um die Aufgaben etwas zu vereinfachen. BlingBlingJS wird für prägnante DOM-Abfragen und die einfache Einrichtung von Event-Listenern verwendet, während roving-ux die barrierefreie Tastatur- und Gamepad-Interaktion für das Pop-up erleichtert.
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 Benutzeroberfläche zu aktualisieren.
Roving-Index
Wenn eine Tastatur oder ein Screenreader den Fokus auf .gui-popup-button
legt, möchten wir den Fokus an die erste (oder zuletzt fokussierte) Schaltfläche in .gui-popup
weiterleiten. Die Bibliothek unterstützt uns dabei mit den Parametern element
und target
.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
Das Element übergibt den Fokus nun an die untergeordneten <button>
-Elemente und ermöglicht die Standardnavigation mit den Pfeiltasten, um Optionen zu durchsuchen.
aria-expanded
umschalten
Während es visuell offensichtlich ist, dass ein Pop-up ein- und ausgeblendet wird, benötigt ein Screenreader mehr als nur visuelle Hinweise. Hier wird JavaScript verwendet, um die CSS-gesteuerte :focus-within
-Interaktion zu ergänzen, indem ein für Screenreader geeignetes Attribut umgeschaltet wird.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Escape
-Taste aktivieren
Der Fokus des Nutzers wurde absichtlich auf eine Falle gelenkt. Daher müssen wir eine Möglichkeit zum Verlassen der Falle bieten. Am häufigsten wird die Verwendung des Escape
-Schlüssels zugelassen.
Dazu müssen Sie auf Tastendrücke auf der Pop-up-Schaltfläche achten, da alle Tastaturereignisse für untergeordnete Elemente an dieses übergeordnete Element weitergeleitet werden.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Wenn die Pop-up-Schaltfläche Escape
-Tastendrücke erkennt, wird der Fokus mit blur()
entfernt.
Klicks auf geteilte Schaltflächen
Wenn der Nutzer auf die Schaltflächen klickt, tippt oder über die Tastatur mit ihnen interagiert, muss die Anwendung die entsprechende Aktion ausführen. Auch hier wird wieder Event Bubbling verwendet, diesmal jedoch für den .gui-split-button
-Container, um Schaltflächenklicks aus einem untergeordneten Pop-up oder der primären Aktion abzufangen.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
Fazit
Jetzt wissen Sie, wie ich es gemacht habe. Wie würden Sie vorgehen? 🙂
Wir möchten unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, die das Web bietet. Erstelle eine Demo, schick mir einen Tweet mit den Links und ich füge sie unten im Bereich „Community-Remixe“ hinzu.