Una panoramica di base su come creare un componente accessibile con pulsante di suddivisione.
In questo post, voglio condividere le riflessioni su un modo per creare un pulsante di suddivisione . Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
I pulsanti divisi sono pulsanti che nascondono un pulsante principale e un elenco di pulsanti aggiuntivi. Sono utili per esporre un'azione comune durante la nidificazione di un'azione secondaria, utilizzata meno frequentemente azioni finché non sono necessarie. Un pulsante di suddivisione può essere cruciale per una progettazione impegnativa sembra minima. Un pulsante di suddivisione avanzato potrebbe persino memorizzare l'ultima azione dell'utente e a ricoprire la prima posizione.
Un pulsante di suddivisione comune è disponibile nell'applicazione di posta elettronica. L'azione principale viene inviato, ma forse puoi inviarla in un secondo momento o salvare una bozza:
L'area di azione condivisa è utile perché l'utente non ha bisogno di guardarsi intorno. Loro tieni presente che nel pulsante di suddivisione sono contenute azioni essenziali per le email.
Parti
Analizziamo le parti essenziali di un pulsante di suddivisione prima di parlarne orchestrazione complessiva e l'esperienza utente finale. Accessibilità di VisBug lo strumento di ispezione è utile per mostrare una vista macro del componente, degli aspetti del codice HTML, dello stile e dell'accessibilità per ogni parte principale.
Contenitore pulsante di suddivisione di primo livello
Il componente di livello più elevato è una flexbox in linea, con una classe
gui-split-button
, contenente l'azione principale
e .gui-popup-button
.
Il pulsante di azione principale
L'elemento <button>
inizialmente visibile e attivabile si inserisce nel contenitore con
due forme di angoli corrispondenti
elemento attivo,
passa il mouse sopra e
interazioni attive per
contenuti in .gui-split-button
.
Pulsante di attivazione/disattivazione popup
Il "pulsante popup" di assistenza è l'attivazione e l'indicazione dell'elenco di
pulsanti secondari. Tieni presente che non è un <button>
e non è attivabile. Tuttavia,
è l'ancoraggio di posizionamento per .gui-popup
e l'host per :focus-within
utilizzato
per presentare il popup.
Scheda popup
Questa è una carta mobile secondaria all'ancoraggio
.gui-popup-button
, valore assoluto e
a capo semanticamente nell'elenco di pulsanti.
Le azioni secondarie
Un elemento <button>
attivabile con dimensioni dei caratteri leggermente inferiori rispetto a principale
pulsante di azione include un'icona e un omaggio
stile al pulsante principale.
Proprietà personalizzate
Le seguenti variabili aiutano a creare un'armonia dei colori e un posto centrale per modificare i valori utilizzati nel componente.
@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%);
}
}
Layout e colore
Aumento
L'elemento inizia come <div>
con un nome di classe personalizzato.
<div class="gui-split-button"></div>
Aggiungi il pulsante principale e gli elementi .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>
Osserva gli attributi aria aria-haspopup
e aria-expanded
. Questi segnali sono
è fondamentale che gli screen reader siano consapevoli della funzionalità e dello stato della suddivisione
l'esperienza dei pulsanti. L'attributo title
è utile per tutti.
Aggiungi un'icona <svg>
e l'elemento contenitore .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>
Per un facile posizionamento del popup, .gui-popup
è un elemento secondario del pulsante
la espande. L'unico problema di questa strategia è l'.gui-split-button
non può utilizzare overflow: hidden
, poiché il popup non verrà più visualizzato
visivamente presente.
Un elemento <ul>
pieno di contenuti <li><button>
si annuncerà come "pulsante"
elenco" agli screen reader, che sono
appunto l'interfaccia presentata.
<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>
Per dare un tocco di originalità e divertimento con i colori, ho aggiunto delle icone ai pulsanti secondari da https://heroicons.com. Le icone sono facoltative per entrambi i pulsanti principali e secondari.
<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>
Stili
Grazie all'HTML e ai contenuti, gli stili sono pronti per fornire colore e layout.
Definizione dello stile del contenitore del pulsante di suddivisione
Un tipo di visualizzazione inline-flex
è ideale per questo componente di wrapping in quanto
devono essere in linea con altri pulsanti, azioni o elementi divisi.
.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;
}
Lo stile <button>
I pulsanti sono molto bravi a nascondere la quantità di codice richiesta. Potresti dover annullare o sostituire gli stili predefiniti del browser, ma dovrai anche applicare l'ereditarietà, aggiungere stati di interazione e adattarsi alle varie preferenze e tipi di input. Gli stili dei pulsanti si aggiungono rapidamente.
Questi pulsanti sono diversi dai normali pulsanti perché condividono uno sfondo con un elemento principale. In genere, un pulsante è proprietario dello sfondo e del colore del testo. Queste persone, tuttavia, lo condividono e applicano solo il proprio sfondo all'interazione.
.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;
}
Aggiungere stati di interazione con alcuni CSS pseudo-classi e l'uso della funzione di corrispondenza proprietà personalizzate per lo stato:
.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);
}
}
Per completare l'effetto di progettazione, il pulsante principale richiede alcuni stili speciali:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
Infine, per aggiungere un tocco di stile, al pulsante e all'icona del tema chiaro vengono visualizzati 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 ottimo pulsante ha prestato attenzione alle microinterazioni e ai piccoli dettagli.
Una nota su :focus-visible
Nota come gli stili dei pulsanti usano :focus-visible
anziché :focus
. :focus
è un aspetto fondamentale per rendere un'interfaccia utente accessibile, ma
svantaggio: non sa se l'utente abbia bisogno di vederlo o meno
non verrà applicata a qualsiasi focus.
Il video seguente tenta di scomporre questa microinterazione per mostrare come
:focus-visible
è un'alternativa intelligente.
Definizione dello stile del pulsante popup
Un flexbox 4ch
per centrare un'icona e ancorare un elenco di pulsanti popup. Mi piace
pulsante principale, rimane trasparente fino a quando non si passa il mouse sopra o si interagisce
e allungato per riempire.
.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);
}
Livelli di passaggio del mouse, stato attivo e stato attivo con CSS
Nesting e
Selettore funzionale di :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);
}
}
Questi stili sono l'hook principale per mostrare e nascondere il popup. Quando
.gui-popup-button
ha focus
su uno qualsiasi dei suoi elementi secondari, impostato opacity
, posizione
e pointer-events
, sull'icona e sul popup.
.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;
}
}
}
Una volta completati gli stili di entrata e uscita, l'ultimo pezzo deve condizionare trasformazioni della transizione in base alle preferenze di movimento dell'utente:
.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 occhio attento al codice potrebbe notare che la transizione dell'opacità è ancora in corso per gli utenti che preferiscono la riduzione del movimento.
Definizione dello stile del popup
L'elemento .gui-popup
è un elenco di pulsanti mobili di schede che utilizzano proprietà personalizzate
e relative, che siano leggermente più piccole, in modo interattivo
e sul brand con l'uso del colore. Nota che le icone hanno meno contrasto,
sono più sottili e l'ombra ha un pizzico di blu. Come per i pulsanti,
L'efficace UI e UX è il risultato di questi piccoli dettagli che si accumulano.
.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%))
;
}
Alle icone e ai pulsanti vengono assegnati colori del brand per creare uno stile più piacevole in ogni ambiente e carta a tema chiaro:
.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%;
}
}
Il popup con tema scuro presenta l'aggiunta di ombre per testo e icone, oltre a un ombra riquadro intensa:
.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));
}
}
}
Stili generici di icona <svg>
Tutte le icone hanno dimensioni relativamente al pulsante font-size
in cui sono utilizzate da
utilizzando l'unità ch
come
inline-size
. A ciascuno di essi sono inoltre stati assegnati alcuni stili per delineare più facilmente le icone
liscia.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
Layout da destra a sinistra
Le proprietà logiche svolgono tutte le attività complesse.
Ecco l'elenco di proprietà logiche utilizzate:
- display: inline-flex
crea un elemento flessibile incorporato.
- padding-block
e padding-inline
in coppia, anziché padding
in forma breve, puoi sfruttare i vantaggi
di riempire i lati logici.
- border-end-start-radius
e
amici
angoli arrotondati in base alla direzione del documento.
- inline-size
anziché width
garantisce che le dimensioni non siano legate alle dimensioni fisiche.
- border-inline-start
aggiunge un bordo all'inizio, che potrebbe essere a destra o a sinistra, a seconda della direzione dello script.
JavaScript
Quasi tutto il codice JavaScript che segue ha lo scopo di migliorare l'accessibilità. Due dei miei librerie helper per semplificare le attività. BlingBlingJS è utilizzato per termini concisi Query DOM e facile configurazione del listener di eventi, mentre roving-ux facilita l'accesso le interazioni da tastiera e gamepad per il popup.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
Dopo aver importato le librerie precedenti e gli elementi selezionati e salvati in l'upgrade dell'esperienza è ad alcune funzioni di cui mancano alcune funzioni.
Indice mobile
Quando una tastiera o uno screen reader mette a fuoco .gui-popup-button
, vogliamo
inoltra l'elemento attivo al primo pulsante (o all'ultimo pulsante attivo) nella
.gui-popup
. La raccolta ci aiuta a fare questo con element
e target
parametri.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
Ora l'elemento passa lo stato attivo ai <button>
secondari target e attiva
i tasti freccia standard consentono di sfogliare le opzioni.
Attivazione/disattivazione di aria-expanded
Sebbene sia visivamente evidente che un popup viene visualizzato o nascosto, uno screen reader ha bisogno di qualcosa in più che di elementi visivi. Qui viene utilizzato JavaScript a complemento dell'interazione :focus-within
guidata da CSS attivando/disattivando l'attributo appropriato di uno screen reader.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Abilitazione della chiave Escape
in corso...
Il focus dell'utente è stato inviato intenzionalmente a una trappola, il che significa che dobbiamo
che offrono un modo per andarsene. Il modo più comune è consentire l'utilizzo della chiave Escape
.
A tale scopo, controlla i tasti premuti sul pulsante popup, dato che gli eventi della tastiera
i bambini visualizzeranno un fumetto con questo genitore.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Se il pulsante popup vede la pressione di un tasto Escape
, lo stato attivo si rimuove da se stesso
con
blur()
Clic sul pulsante suddivisi
Infine, se l'utente fa clic, tocca o interagisce con la tastiera, il
dell'applicazione deve eseguire l'azione appropriata. Viene utilizzato il bubbling degli eventi
di nuovo qui, ma questa volta sul container .gui-split-button
, per individuare il pulsante
i clic da un popup secondario o dall'azione principale.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
Conclusione
Ora che sai come ci ho fatto, come faresti‽ 🙂
Diversificaamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami con i link e la aggiungerò alla sezione dei remix della community qui sotto.