Creazione di un componente Pulsante di suddivisione

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.

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:

Un esempio di pulsante di suddivisione, come si vede in un'applicazione email.

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.

Gli elementi HTML che compongono il pulsante di suddivisione.

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.

La classe gui-split-button ispezionata e mostra le proprietà CSS utilizzate in questa classe.

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.

La finestra di controllo che mostra le regole CSS per l&#39;elemento pulsante.

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.

La finestra di controllo che mostra le regole CSS per la classe gui-popup-button.

Scheda popup

Questa è una carta mobile secondaria all'ancoraggio .gui-popup-button, valore assoluto e a capo semanticamente nell'elenco di pulsanti.

La finestra di controllo che mostra le regole CSS per il popup gui-class della classe

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.

La finestra di controllo che mostra le regole CSS per l&#39;elemento pulsante.

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;
}

Il pulsante Suddividi.

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.

La freccia del pulsante di suddivisione utilizzata per attivare il popup.

.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.

Un elemento mobile della carta.

.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:

Link e icone per il pagamento, Quick Pay e Salva per dopo.

.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:

Il popup con il tema scuro.

.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.

Remix della community