Komponente für geteilte Schaltfläche erstellen

Ein grundlegender Überblick über die Erstellung einer barrierefreien Split-Button-Komponente.

In diesem Beitrag möchte ich auf Ihre Gedanken zum Erstellen einer geteilten Schaltfläche eingehen . Demo ansehen.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> Demo

Falls Sie Videos bevorzugen, finden Sie hier eine YouTube-Version dieses Beitrags:

Übersicht

Geteilte Schaltflächen sind Schaltflächen die eine primäre Schaltfläche und eine Liste zusätzlicher Schaltflächen verbergen. Sie sind nützlich zum Anzeigen einer gemeinsamen Aktion beim Verschachteln sekundärer, weniger häufig verwendeter Aktionen Maßnahmen ergreifen, bis sie benötigt werden. Eine geteilte Schaltfläche kann entscheidend sein, sich minimal anfühlen. Eine erweiterte Schaltfläche „Aufteilen“ kann sogar die letzte Nutzeraktion speichern. und es in die Hauptposition bringen.

Eine gängige Aufteilungsschaltfläche finden Sie in Ihrer E-Mail-Anwendung. Die primäre Aktion Sie können die Nachricht aber auch später senden oder als Entwurf speichern:

Ein Beispiel für die Schaltfläche „Aufteilen“ aus einer E-Mail-Anwendung.

Der gemeinsame Aktionsbereich ist eine tolle Sache, da die Nutzenden sich nicht umsehen müssen. Sie Wichtige E-Mail-Aktionen sind in der Aufteilungsschaltfläche enthalten.

Teile

Sehen wir uns die wesentlichen Elemente einer geteilten Schaltfläche genauer an, bevor wir die gesamte Orchestrierung und die endgültige User Experience. Bedienungshilfen von VisBug Prüftool wird hier verwendet, um eine Makroansicht der Komponente zu zeigen, des HTML-Codes, des Stils und der Zugänglichkeit für jeden Hauptteil.

Die HTML-Elemente, aus denen die Schaltfläche zum Teilen besteht.

Container für die geteilte Schaltfläche der obersten Ebene

Die Komponente auf der höchsten Ebene ist eine Inline-Flexbox mit der Klasse gui-split-button mit der primären Aktion und die .gui-popup-button.

Die Klasse „gui-split-button“ wurde geprüft und zeigt die in dieser Klasse verwendeten CSS-Eigenschaften.

Die primäre Aktionsschaltfläche

Das anfänglich sichtbare und fokussierbare <button> passt in den Container. zwei übereinstimmende Eckenformen für Focus [Fokus], Mauszeiger darüberbewegen und aktiven Interaktionen erscheinen in .gui-split-button.

Der Inspector, der die CSS-Regeln für das Schaltflächenelement anzeigt.

Die Ein/Aus-Schaltfläche für das Pop-up

Die Pop-up-Schaltfläche unterstützende Elemente sind zur Aktivierung und Anspielung auf die Liste der sekundäre Schaltflächen. Es ist kein <button> und es ist nicht fokussierbar. Sie können jedoch Das ist der Positionierungsanker für .gui-popup und der Host für :focus-within. um das Pop-up zu präsentieren.

Der Inspector, der die CSS-Regeln für die Klasse „gui-popup-button“ anzeigt.

Die Pop-up-Karte

Dies ist eine dem Anker untergeordnete Floating-Karte .gui-popup-button, positionierte absolute und die Schaltflächenliste semantisch verpackt.

Der Inspector, der die CSS-Regeln für die Klasse gui-popup anzeigt

Sekundäre Aktionen

Eine fokussierbare <button> mit einer etwas kleineren Schriftgröße als die primäre Aktionsschaltfläche enthält ein Symbol und eine ergänzende auf die primäre Schaltfläche hinzu.

Der Inspector, der die CSS-Regeln für das Schaltflächenelement anzeigt.

Benutzerdefinierte Eigenschaften

Die folgenden Variablen tragen dazu bei, eine harmonische Farbe zu erzeugen und eine zentrale Stelle zu schaffen, in der Komponente verwendete Werte ändern.

@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 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 ist wichtig, damit Screenreader die Möglichkeiten und den Status der Aufteilung Schaltflächen. Das Attribut title ist für alle Nutzer 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 untergeordnet, die erweitert es. Der einzige Haken bei dieser Strategie ist der .gui-split-button Der Container kann overflow: hidden nicht verwenden, da dadurch das Pop-up-Fenster reduziert wird. visuell dargestellt wird.

Ein <ul> mit <li><button> Inhalten meldet sich als „Schaltfläche“ Liste“ Screenreadern, d. h. genau die dort dargestellte Benutzeroberfläche.

<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 das gewisse Etwas und um Farbe zu haben, habe ich den sekundären Schaltflächen Symbole hinzugefügt, https://heroicons.com an. Symbole sind für beide optional die primäre und die sekundäre Schaltfläche.

<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

Sobald HTML und Inhalte vorhanden sind, können mithilfe von Stilen Farben und Layout bereitgestellt werden.

Container für die Schaltfläche „Aufteilen“ gestalten

Der Displaytyp inline-flex eignet sich gut für diese Wrapping-Komponente, sollte zusammen mit anderen geteilten Schaltflächen, Aktionen oder Elementen passen.

.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 Schaltfläche „Teilen“.

Der <button>-Stil

Schaltflächen sind sehr gut dafür geeignet, zu verschleiern, wie viel Code erforderlich ist. Möglicherweise müssen Sie Browser-Standardstile rückgängig machen oder ersetzen. Sie müssen aber auch Vererbung, das Hinzufügen von Interaktionszuständen und die Anpassung an verschiedene Eingabetypen. Schaltflächenstile lassen sich schnell addieren.

Diese Schaltflächen unterscheiden sich von normalen Schaltflächen, da sie einen gemeinsamen Hintergrund haben. mit einem übergeordneten Element. Normalerweise besitzt eine Schaltfläche den Hintergrund und die Textfarbe. Sie 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;
}

Interaktionsstatus mit wenigen CSS hinzufügen Pseudoklassen und Abgleich verwenden benutzerdefinierte Eigenschaften für den Bundesstaat:

.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 Hauptschaltflä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);
  }
}

Die Schaltfläche und das Symbol für das helle Design werden ebenfalls shadow auf:

.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 winzigen Details geachtet.

Hinweis zu :focus-visible

Beachten Sie, dass für die Schaltflächenstile :focus-visible anstelle von :focus verwendet wird. :focus ist ein entscheidender Faktor für die Entwicklung einer barrierefreien Benutzeroberfläche. Es ist nicht intelligent, ob die Nutzenden es sehen oder gilt für jeden Fokus.

Im folgenden Video wird versucht, diese Mikrointeraktion aufzuschlüsseln, um zu zeigen, :focus-visible ist eine intelligente Alternative.

Pop-up-Schaltfläche gestalten

Eine 4ch-Flexbox zum Zentrieren eines Symbols und Verankern einer Pop-up-Schaltflächenliste. Gefällt mir die primäre Schaltfläche ist, bleibt sie transparent, bis sie den Mauszeiger auf eine andere Schaltfläche bewegt oder darauf interagiert wird. und gestreckt bis zum Füllen.

Der Pfeilteil der Schaltfläche zum Teilen, der zum Auslösen des Pop-ups verwendet 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);
}

Ebene im Hover-, fokussierten und aktiven Status mit CSS Verschachtelung und die Funktionsauswahl für :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);
  }
}

Diese Stile sind der primäre Aufhänger zum Ein- und Ausblenden des Pop-ups. Wenn der Parameter .gui-popup-button hat focus für eines seiner untergeordneten Elemente, legt opacity, Position fest und pointer-events für das Symbol und das Pop-up.

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

Nach Abschluss der Innen- und Außenstile ist der letzte Teil die bedingte Übergang transformiert je nach den Bewegungseinstellungen des Nutzers:

.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 genauer Blick auf den Code würde feststellen, dass die Opazität für Nutzer noch umgestellt wird. die eingeschränkte Bewegung bevorzugen.

Pop-up gestalten

Das .gui-popup-Element ist eine Schaltflächenliste einer unverankerten Karte mit benutzerdefinierten Eigenschaften. und relative Einheiten werden subtil kleiner angezeigt und interaktiv mit den primären Schaltfläche und auf die Marke durch ihre Verwendung von Farbe. Beachten Sie, dass die Symbole weniger Kontrast haben, sind dünner und der Schatten hat einen Hauch von Markenblau. Wie bei Schaltflächen Eine gute Benutzeroberfläche und UX ist das Ergebnis dieser kleinen Details.

Ein unverankertes Kartenelement.

.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 in jedem Dunkeln ansprechend zu wirken. Karte mit hellem Design:

Links und Symbole für „Bezahlvorgang“, „Schnell bezahlen“ und „Für später speichern“.

.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-Fenster „Dunkles Design“ enthält Text- und Symbolschatten und einen etwas mehr Intensiver Box-Schatten:

Das Pop-up im dunklen Design.

.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 haben eine relativ große Größe an der Schaltfläche font-size, in der sie von Dabei wird die Einheit ch als inline-size Jeder hat auch Stile, mit denen die Symbole flüssiger machen.

.gui-split-button svg {
  inline-size: 2ch;
  box-sizing: content-box;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2px;
}

Linksläufiges Layout

Logische Eigenschaften übernehmen die komplexe Arbeit. Hier ist die Liste der verwendeten logischen Eigenschaften: – display: inline-flex erstellt ein flexibles Inline-Element. – padding-block und padding-inline als Paar, statt padding die Vorteile der Auffüllung der logischen Seiten. – border-end-start-radius und Freunde tun Folgendes: die Ecken je nach Dokumentrichtung abgerundet werden. - Mit inline-size statt width wird sichergestellt, dass die Größe nicht an die physischen Maße gebunden ist. – border-inline-start fügt am Anfang einen Rahmen hinzu. Dieser kann sich je nach Skriptrichtung auf der rechten oder linken Seite befinden.

JavaScript

Fast alle der folgenden JavaScript-Codes sollen die Barrierefreiheit verbessern. Zwei meiner Hilfsbibliotheken verwendet werden, um die Aufgaben zu erleichtern. BlingBlingJS ist eine Kurzfassung. DOM-Abfragen und einfache Einrichtung des Event-Listeners roving-ux erleichtert 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')

Wenn die obigen Bibliotheken importiert und die Elemente ausgewählt und in folgendem Ordner gespeichert wurden: Variablen. Ein Upgrade der Websitevariante ist nur ein paar Funktionen davon entfernt, vollständig zu sein.

Roving Index

Wenn eine Tastatur oder ein Screenreader .gui-popup-button fokussiert, möchten wir Fokus auf die erste (oder zuletzt fokussierte) Schaltfläche im .gui-popup. Die Bibliothek hilft uns dabei, dies mithilfe von element und target zu tun. Parameter.

popupButtons.forEach(element =>
  rovingIndex({
    element,
    target: 'button',
  }))

Das Element übergibt jetzt den Fokus an die untergeordneten <button>-Ziele und aktiviert Standardnavigation mit Pfeiltasten, um Optionen zu durchsuchen.

aria-expanded wird umgeschaltet

Obwohl es visuell erkennbar ist, dass ein Pop-up angezeigt und ausgeblendet wird, benötigt ein Screenreader mehr als visuelle Hinweise. Hier wird JavaScript verwendet, um die CSS-gesteuerte :focus-within-Interaktion zu ergänzen, indem ein für den 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 wird aktiviert

Der Fokus des Nutzers wurde absichtlich in eine Falle gelegt, wir müssen ihn also eine Möglichkeit zum Verlassen. Die gängigste Methode ist die Verwendung des Schlüssels Escape. Achten Sie dazu auf Tastendruck auf der Pop-up-Schaltfläche, da alle Tastaturereignisse untergeordneten Elementen zu diesem übergeordneten Element.

popupButtons.on('keyup', e => {
  if (e.code === 'Escape')
    e.target.blur()
})

Wenn Escape-Tasten auf die Pop-up-Schaltfläche drücken, wird der Fokus von sich selbst entfernt mit blur()

Klicks auf die Schaltfläche „Aufteilen“

Wenn Nutzende auf die Schaltflächen klicken, tippen oder die Tastatur mit den Schaltflächen interagieren, die entsprechende Aktion ausführen muss. Ereignis-Bubbling wird verwendet wieder hier, aber diesmal im Container .gui-split-button, um die Schaltfläche zu fangen Klicks von einem untergeordneten Pop-up oder der primären Aktion.

splitButtons.on('click', event => {
  if (event.target.nodeName !== 'BUTTON') return
  console.info(event.target.innerText)
})

Fazit

Jetzt, wo du weißt, wie ich es gemacht habe, wie würdest du... ‽ 🙂

Lassen Sie uns unsere Herangehensweisen diversifizieren und alle Möglichkeiten kennenlernen, wie wir das Web entwickeln können. Erstelle eine Demo, twittere mir Links und ich füge sie hinzu im Abschnitt „Community-Remixe“ weiter unten.

Community-Remixe