Komponente für die Kurzinfo erstellen

Ein grundlegender Überblick darüber, wie Sie ein benutzerdefiniertes, farbanpassungsfähiges und barrierefreies Kurzinfo-Element erstellen.

In diesem Beitrag möchte ich dir mitteilen, wie man ein farbanpassungsfähiges und barrierefreies <tool-tip>-Element erstellt. Probieren Sie die Demo aus und sehen Sie sich den Quellcode an.

Eine Kurzinfo wird für eine Vielzahl von Beispielen und Farbschemas angezeigt

Falls du lieber ein Video hast, findest du hier eine YouTube-Version dieses Beitrags:

Überblick

Eine Kurzinfo ist ein nicht modales, nicht blockierendes, nicht interaktives Overlay, das zusätzliche Informationen zu Benutzeroberflächen enthält. Es ist standardmäßig ausgeblendet und wird eingeblendet, wenn der Mauszeiger auf ein verknüpftes Element bewegt wird oder der Fokus darauf liegt. Eine Kurzinfo kann nicht direkt ausgewählt oder bearbeitet werden. Kurzinfos ersetzen keine Labels oder andere wichtige Informationen. Nutzer sollten ihre Aufgabe ohne Kurzinfo vollständig erledigen können.

Empfohlen: Die Eingaben immer mit Labels versehen.
Falsch: Kurzinfos anstelle von Labels verwenden

Tipp und Kurzinfo ein-/ausblenden

Wie bei vielen Komponenten wird auch hier unterschiedlich beschrieben, was eine Kurzinfo ist, z. B. in MDN, WAI ARIA, Sarah Higley und Inklusive Komponenten. Mir gefällt die Trennung zwischen Tooltipps und Ein-/Aus-Schaltflächen. Eine Kurzinfo sollte nicht interaktive ergänzende Informationen enthalten, während eine Ein/Aus-Schaltfläche Interaktivität und wichtige Informationen enthalten kann. Der Hauptgrund für die Spaltung ist die Barrierefreiheit: Wie gehen Nutzer zum Pop-up und haben Zugriff auf die darin enthaltenen Informationen und Schaltflächen? Die Ein-/Aus-Schaltflächen werden schnell komplex.

In diesem Video sehen Sie eine Ein/Aus-Schaltfläche von der Designcember-Website. Ein Overlay mit Interaktivität, das der Nutzer erst anheften und erkunden und dann durch Schließen der LED oder der Esc-Taste wieder schließen kann:

Diese GUI-Challenge nutzte eine Kurzinfo, um fast alles mit CSS zu erledigen. Hier erfährst du, wie dies erstellt wird.

Markup

Ich habe mich für die Verwendung eines benutzerdefinierten Elements <tool-tip> entschieden. Sie müssen keine benutzerdefinierten Elemente in Webkomponenten umwandeln, wenn sie das nicht möchten. Der Browser behandelt <foo-bar> wie einen <div>. Sie können sich ein benutzerdefiniertes Element wie einen Klassennamen mit weniger Spezifität vorstellen. Es wird kein JavaScript verwendet.

<tool-tip>A tooltip</tool-tip>

Dies ist wie ein div-Element mit Text. [role="tooltip"] einbinden.

<tool-tip role="tooltip">A tooltip</tool-tip>

Für Screenreader wird er jetzt als Kurzinfo erkannt. Im folgenden Beispiel sehen Sie, dass das erste Linkelement ein Kurzinfo-Element in seiner Baumstruktur hat und das zweite nicht. Das zweite hat diese Rolle nicht. Im Bereich „Styles“ verbessern wir diese Baumansicht.

Screenshot der Barrierefreiheits-Baumansicht in den Chrome-Entwicklertools, die den HTML-Code darstellt Zeigt einen fokussierbaren Link mit dem Text „top“ und der Kurzinfo „Hey, a tooltip!“ an. Innerhalb davon befindet sich statischer Text „top“ und ein Kurzinfo-Element.

Als Nächstes muss die Kurzinfo nicht fokussierbar sein. Wenn ein Screenreader die Rolle für die Kurzinfo nicht versteht, können Nutzer <tool-tip> fokussieren, um den Inhalt zu lesen. Für die Nutzer ist das nicht erforderlich. Screenreader hängen den Inhalt an das übergeordnete Element an. Daher ist kein Fokus erforderlich, um barrierefrei zu sein. Hier können wir mit inert dafür sorgen, dass keine Nutzer versehentlich die Kurzinfo in ihrem Tab-Ablauf finden:

<tool-tip inert role="tooltip">A tooltip</tool-tip>

Ein weiterer Screenshot der Barrierefreiheitsstruktur in den Chrome-Entwicklertools, in der dieses Mal das Kurzinfo-Element fehlt.

Anschließend habe ich Attribute als Schnittstelle zum Angeben der Position der Kurzinfo verwendet. Standardmäßig übernehmen alle <tool-tip>s eine „obere“ Position. Sie kann jedoch durch Hinzufügen von tip-position für ein Element angepasst werden:

<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>

Screenshot eines Links mit der Kurzinfo „Kurzinfo“ rechts.

Für solche Situationen neige ich dazu, Attribute anstelle von Klassen zu verwenden, damit <tool-tip> nicht mehrere Positionen gleichzeitig zugewiesen werden können. Es kann nur eine oder keine geben.

Platzieren Sie <tool-tip>-Elemente innerhalb des Elements, für das Sie eine Kurzinfo bereitstellen möchten. Hier teile ich den alt-Text mit sehenden Nutzern. Dazu platziere ich ein Bild und ein <tool-tip>-Element innerhalb eines <picture>-Elements:

<picture>
  <img alt="The GUI Challenges skull logo" width="100" src="...">
  <tool-tip role="tooltip" tip-position="bottom">
    The <b>GUI Challenges</b> skull logo
  </tool-tip>
</picture>

Screenshot eines Bildes mit der Kurzinfo „The GUI Challenges Schädellogo“.

Hier platziere ich ein <tool-tip> innerhalb eines <abbr>-Elements:

<p>
  The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>

Screenshot eines Absatzes mit unterstrichenem Akronym HTML und der Kurzinfo „Hyper Text Markup Language“.

Barrierefreiheit

Da ich mich dafür entschieden habe, Kurzinfos und keine Ein-/Aus-Schaltflächen zu erstellen, ist dieser Abschnitt viel einfacher. Zunächst möchte ich Ihnen die User Experience vorstellen:

  1. Blenden Sie in begrenzten Gruppenbereichen oder unübersichtlichen Benutzeroberflächen zusätzliche Meldungen aus.
  2. Die Nachricht wird angezeigt, wenn ein Nutzer den Mauszeiger darauf bewegt, den Fokus fokussiert oder Berührungen verwendet, um mit einem Element zu interagieren.
  3. Blenden Sie die Nachricht wieder aus, wenn der Mauszeiger darauf bewegt wird, der Fokus darauf liegt oder die Berührung beendet wird.
  4. Achten Sie außerdem darauf, dass alle Bewegungen reduziert werden, wenn ein Nutzer eine Präferenz für die verringerte Bewegung angegeben hat.

Unser Ziel sind ergänzende On-Demand-Nachrichten. Eine sehende Maus oder Tastaturnutzer kann den Mauszeiger auf die Nachricht bewegen und sie dann mit den Augen lesen. Ein Nutzer ohne Sehbehinderung kann den Fokus auf die Nachricht richten, sodass sie über das Tool hörbar empfangen wird.

Screenshot davon, wie macOS VoiceOver einen Link mit einer Kurzinfo vorliest

Im vorherigen Abschnitt haben wir den Baum für Barrierefreiheit, die Rolle der Kurzinfo und die Integrität behandelt. Jetzt müssen Sie ihn testen und prüfen, ob der Nutzer die Kurzinfo richtig anzeigt. Beim Testen konnte nicht festgestellt werden, welcher Teil der hörbaren Nachricht eine Kurzinfo ist. Auch beim Debuggen im Baum für Barrierefreiheit ist zu sehen, dass der Linktext „top“ zusammen mit „Look, tooltips!“ ohne zu zögern ausgeführt wird. Der Screenreader bricht den Text nicht ab und erkennt ihn nicht als Kurzinfo.

Screenshot der Barrierefreiheitsansicht der Chrome-Entwicklertools mit dem Linktext „top Hey, a tooltip!“

Wenn Sie <tool-tip> ein nur für Screenreader vorgesehenes Pseudoelement hinzufügen, können wir unseren eigenen Aufforderungstext für Nutzer ohne Sehvermögen hinzufügen.

&::before {
  content: "; Has tooltip: ";
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
}

Unten sehen Sie die aktualisierte Struktur der Bedienungshilfen, die nach dem Linktext jetzt ein Semikolon und die Aufforderung „Hat Kurzinfo:“ enthält.

Ein aktualisierter Screenshot der Barrierefreiheits-Baumansicht in den Chrome-Entwicklertools mit verbesserter Formulierung („top“ und Kurzinfo: „Hey, a tooltip!“).

Wenn ein Nutzer eines Screenreaders den Link fokussiert, wird „oben“ angezeigt, eine kurze Pause macht und dann die Meldung „Enthält Kurzinfo: Look, Kurzinfos“. Dies gibt Nutzenden ein paar nützliche UX-Hinweise. Dadurch lassen sich Linktext und Kurzinfo gut trennen. Wenn „Mit Kurzinfo“ angesagt wird, kann ein Screenreader-Nutzer diese ganz einfach abbrechen, wenn er sie bereits gehört hat. Wie Sie bereits gesehen haben, erinnert es daran, dass die Maus schnell bewegt und die Maus darüber bewegt wird. Das fühlte sich wie eine schöne UX-Gleichheit an.

Stile

Das <tool-tip>-Element ist dem Element, für das es ergänzende Mitteilungen darstellt, untergeordnet. Beginnen wir also mit den Grundlagen des Overlay-Effekts. Entfernen Sie ihn mit position absolute aus dem Dokumentablauf:

tool-tip {
  position: absolute;
  z-index: 1;
}

Wenn das übergeordnete Element kein Stapelkontext ist, wird die Kurzinfo an der nächstgelegenen Position angezeigt, was nicht unseren Anforderungen entspricht. Für den Block gibt es einen neuen Selektor :has():

Unterstützte Browser

  • 105
  • 105
  • 121
  • 15,4

Quelle

:has(> tool-tip) {
  position: relative;
}

Machen Sie sich keine allzu großen Gedanken über die Browserunterstützung. Denken Sie daran, dass diese Kurzinfos ergänzend sind. Wenn sie nicht funktionieren, sollte es kein Problem sein. Zweitens stellen wir im JavaScript-Bereich ein Skript bereit, mit dem die Funktionen erweitert werden, die wir für Browser ohne :has()-Unterstützung benötigen.

Als Nächstes wollen wir die Kurzinfos nicht interaktiv gestalten, damit sie keine Zeigerereignisse von ihrem übergeordneten Element stehlen:

tool-tip {
  …
  pointer-events: none;
  user-select: none;
}

Blenden Sie dann die Kurzinfo mit Deckkraft aus, damit wir sie mit einer Überblendung versehen können:

tool-tip {
  opacity: 0;
}

:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
  opacity: 1;
}

:is() und :has() sind hier die schwierige Aufgabe. tool-tip, die übergeordnete Elemente enthalten, werden auf Nutzerinteraktivität aufmerksam gemacht, um die Sichtbarkeit einer untergeordneten Kurzinfo ein- und auszuschalten. Nutzer mit Mausbewegungen, Tastatur- und Screenreadern können den Fokus fokussieren und Touch-Nutzer können tippen.

Da das Overlay zum Ein- und Ausblenden auch für sehende Nutzer funktioniert, ist es an der Zeit, einige Stile für die Themen festzulegen, die Dreiecksform zu positionieren und der Blase hinzuzufügen. Für die folgenden Stile werden zuerst benutzerdefinierte Eigenschaften verwendet. Sie bauen auf dem vorherigen Punkt auf, fügen aber auch Schatten, Typografie und Farben hinzu, sodass es wie eine unverankerte Kurzinfo aussieht:

Screenshot der Kurzinfo im dunklen Modus, der über dem Link „block-start“ schwebt.

tool-tip {
  --_p-inline: 1.5ch;
  --_p-block: .75ch;
  --_triangle-size: 7px;
  --_bg: hsl(0 0% 20%);
  --_shadow-alpha: 50%;

  --_bottom-tip: conic-gradient(from -30deg at bottom, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) bottom / 100% 50% no-repeat;
  --_top-tip: conic-gradient(from 150deg at top, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) top / 100% 50% no-repeat;
  --_right-tip: conic-gradient(from -120deg at right, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) right / 50% 100% no-repeat;
  --_left-tip: conic-gradient(from 60deg at left, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) left / 50% 100% no-repeat;

  pointer-events: none;
  user-select: none;

  opacity: 0;
  transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
  transition: opacity .2s ease, transform .2s ease;

  position: absolute;
  z-index: 1;
  inline-size: max-content;
  max-inline-size: 25ch;
  text-align: start;
  font-size: 1rem;
  font-weight: normal;
  line-height: normal;
  line-height: initial;
  padding: var(--_p-block) var(--_p-inline);
  margin: 0;
  border-radius: 5px;
  background: var(--_bg);
  color: CanvasText;
  will-change: filter;
  filter:
    drop-shadow(0 3px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
    drop-shadow(0 12px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}

/* create a stacking context for elements with > tool-tips */
:has(> tool-tip) {
  position: relative;
}

/* when those parent elements have focus, hover, etc */
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
  opacity: 1;
  transition-delay: 200ms;
}

/* prepend some prose for screen readers only */
tool-tip::before {
  content: "; Has tooltip: ";
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
}

/* tooltip shape is a pseudo element so we can cast a shadow */
tool-tip::after {
  content: "";
  background: var(--_bg);
  position: absolute;
  z-index: -1;
  inset: 0;
  mask: var(--_tip);
}

/* top tooltip styles */
tool-tip:is(
  [tip-position="top"],
  [tip-position="block-start"],
  :not([tip-position]),
  [tip-position="bottom"],
  [tip-position="block-end"]
) {
  text-align: center;
}

Designanpassungen

Die Kurzinfo enthält nur wenige Farben, die verwaltet werden müssen, da die Textfarbe über das System-Keyword CanvasText von der Seite übernommen wird. Da wir benutzerdefinierte Eigenschaften zum Speichern der Werte erstellt haben, können wir nur diese benutzerdefinierten Eigenschaften aktualisieren und den Rest dem Thema überlassen:

@media (prefers-color-scheme: light) {
  tool-tip {
    --_bg: white;
    --_shadow-alpha: 15%;
  }
}

Screenshot der hellen und der dunklen Version der Kurzinfo im direkten Vergleich

Beim hellen Design passen wir den Hintergrund an Weiß an und machen die Schatten viel weniger stark, indem wir die Deckkraft anpassen.

Von rechts nach links

Um den Lesemodus von rechts nach links zu unterstützen, speichert eine benutzerdefinierte Eigenschaft den Wert der Dokumentrichtung als Wert von -1 bzw. 1.

tool-tip {
  --isRTL: -1;
}

tool-tip:dir(rtl) {
  --isRTL: 1;
}

Diese Angaben können zur Positionierung der Kurzinfo verwendet werden:

tool-tip[tip-position="top"]) {
  --_x: calc(50% * var(--isRTL));
}

Zur Unterstützung bei der Position des Dreiecks:

tool-tip[tip-position="right"]::after {
  --_tip: var(--_left-tip);
}

tool-tip[tip-position="right"]:dir(rtl)::after {
  --_tip: var(--_right-tip);
}

Kann auch für logische Transformationen in translateX() verwendet werden:

--_x: calc(var(--isRTL) * -3px * -1);

Positionierung der Kurzinfo

Positionieren Sie die Kurzinfo logisch mit den Attributen inset-block oder inset-inline, um sowohl die physischen als auch die logischen Positionen der Kurzinfo zu verarbeiten. Der folgende Code zeigt, wie jede der vier Positionen für linksläufige und linksläufige Richtungen gestaltet wird.

Oben- und Blockstart-Ausrichtung

Ein Screenshot, auf dem der Placement-Unterschied zwischen linksläufiger oberer Position und rechtsläufiger oberer Position zu sehen ist.

tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position])) {
  inset-inline-start: 50%;
  inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
  --_x: calc(50% * var(--isRTL));
}

tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))::after {
  --_tip: var(--_bottom-tip);
  inset-block-end: calc(var(--_triangle-size) * -1);
  border-block-end: var(--_triangle-size) solid transparent;
}

Rechts- und Inline-Ausrichtung

Ein Screenshot, der den Placement-Unterschied zwischen einer rechtsläufigen und einer linksläufigen Inline-Endposition zeigt

tool-tip:is([tip-position="right"], [tip-position="inline-end"]) {
  inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
  inset-block-end: 50%;
  --_y: 50%;
}

tool-tip:is([tip-position="right"], [tip-position="inline-end"])::after {
  --_tip: var(--_left-tip);
  inset-inline-start: calc(var(--_triangle-size) * -1);
  border-inline-start: var(--_triangle-size) solid transparent;
}

tool-tip:is([tip-position="right"], [tip-position="inline-end"]):dir(rtl)::after {
  --_tip: var(--_right-tip);
}

Ausrichtung am unteren Rand und am Blockende

Ein Screenshot, auf dem der Placement-Unterschied zwischen der unteren Position von links nach rechts und der Position des Blockendes von rechts nach links zu sehen ist.

tool-tip:is([tip-position="bottom"], [tip-position="block-end"]) {
  inset-inline-start: 50%;
  inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
  --_x: calc(50% * var(--isRTL));
}

tool-tip:is([tip-position="bottom"], [tip-position="block-end"])::after {
  --_tip: var(--_top-tip);
  inset-block-start: calc(var(--_triangle-size) * -1);
  border-block-start: var(--_triangle-size) solid transparent;
}

Links- und Inline-Start-Ausrichtung

Ein Screenshot, auf dem der Placement-Unterschied zwischen der linksläufigen und der linksläufigen Inline-Startposition zu sehen ist.

tool-tip:is([tip-position="left"], [tip-position="inline-start"]) {
  inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
  inset-block-end: 50%;
  --_y: 50%;
}

tool-tip:is([tip-position="left"], [tip-position="inline-start"])::after {
  --_tip: var(--_right-tip);
  inset-inline-end: calc(var(--_triangle-size) * -1);
  border-inline-end: var(--_triangle-size) solid transparent;
}

tool-tip:is([tip-position="left"], [tip-position="inline-start"]):dir(rtl)::after {
  --_tip: var(--_left-tip);
}

Animation

Bisher haben wir nur die Sichtbarkeit der Kurzinfo ein- oder ausgeschaltet. In diesem Abschnitt geht es um die Animation der Deckkraft für alle Nutzer, da es sich um einen allgemein sicheren Übergang zu reduzierten Bewegungen handelt. Anschließend animieren wir die Transformationsposition, sodass sich die Kurzinfo aus dem übergeordneten Element zu bewegen scheint.

Eine sichere und sinnvolle Standardumstellung

Passen Sie das Kurzinfo-Element so an, dass es überdeckt wird und die Deckkraft übergeht:

tool-tip {
  opacity: 0;
  transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
  transition: opacity .2s ease, transform .2s ease;
}

:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
  opacity: 1;
  transition-delay: 200ms;
}

Bewegung zum Übergang hinzufügen

Für jede Seite kann eine Kurzinfo angezeigt werden. Wenn der Nutzer mit Bewegung einverstanden ist, positionieren Sie die Eigenschaft „translateX“ leicht und geben Sie einen kleinen Abstand, von dem der Nutzer entfernt ist:

@media (prefers-reduced-motion: no-preference) {
  :has(> tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))):not(:hover):not(:focus-visible):not(:active) tool-tip {
    --_y: 3px;
  }

  :has(> tool-tip:is([tip-position="right"], [tip-position="inline-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
    --_x: -3px;
  }

  :has(> tool-tip:is([tip-position="bottom"], [tip-position="block-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
    --_y: -3px;
  }

  :has(> tool-tip:is([tip-position="left"], [tip-position="inline-start"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
    --_x: 3px;
  }
}

Hier wird der Status „out“ (Aus) festgelegt, da sich der Status „in“ (An) auf translateX(0) befindet.

JavaScript

Meiner Meinung nach ist JavaScript optional. Das liegt daran, dass keine dieser Kurzinfos zum Ausführen einer Aufgabe in Ihrer UI erforderlich sein sollte. Wenn also die Kurzinfos vollständig fehlschlagen, ist das kein Problem. Das bedeutet auch, dass wir die Kurzinfos nach und nach verbessert. Schließlich werden alle Browser :has() unterstützen und dieses Script wird nicht mehr unterstützt.

Das Polyfill-Skript erfüllt zwei Dinge, allerdings nur, wenn der Browser :has() nicht unterstützt. Prüfen Sie zuerst, ob :has()-Support verfügbar ist:

if (!CSS.supports('selector(:has(*))')) {
  // do work
}

Suchen Sie als Nächstes nach den übergeordneten Elementen von <tool-tip>s und geben Sie ihnen einen Klassennamen, mit dem Sie arbeiten können:

if (!CSS.supports('selector(:has(*))')) {
  document.querySelectorAll('tool-tip').forEach(tooltip =>
    tooltip.parentNode.classList.add('has_tool-tip'))
}

Als Nächstes injizieren Sie eine Reihe von Stilen, die diesen Klassennamen verwenden, und simulieren die Auswahl :has() für genau das gleiche Verhalten:

if (!CSS.supports('selector(:has(*))')) {
  document.querySelectorAll('tool-tip').forEach(tooltip =>
    tooltip.parentNode.classList.add('has_tool-tip'))

  let styles = document.createElement('style')
  styles.textContent = `
    .has_tool-tip {
      position: relative;
    }
    .has_tool-tip:is(:hover, :focus-visible, :active) > tool-tip {
      opacity: 1;
      transition-delay: 200ms;
    }
  `
  document.head.appendChild(styles)
}

Das ist alles. Jetzt zeigen alle Browser die Kurzinfos an, wenn :has() nicht unterstützt wird.

Fazit

Jetzt, da Sie wissen, wie ich es gemacht habe, wie würden Sie es machen?popupanchor Bis dahin erstelle ich Kurzinfos.

Diversifizieren wir unsere Ansätze und lernen Sie alle Möglichkeiten kennen, wie wir das Web nutzen können.

Erstelle eine Demo und twittere mich über Links, und ich füge sie unten zum Abschnitt über Community-Remixe hinzu.

Community-Remixe

Hier gibt es noch nichts zu sehen.

Ressourcen