Eine grundlegende Übersicht zum Erstellen eines benutzerdefinierten Elements für farbadaptierte und barrierefreie Kurzinfos.
In diesem Beitrag möchte ich meine Gedanken dazu teilen, wie Sie ein farbadaptives und barrierefreies benutzerdefiniertes <tool-tip>
-Element erstellen. Probieren Sie die Demo aus und sehen Sie sich die Quelle an.
Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
Ein Kurzinfofeld ist ein nicht modales, nicht blockierendes, nicht interaktives Overlay mit zusätzlichen Informationen zu Benutzeroberflächen. Es ist standardmäßig ausgeblendet und wird eingeblendet, wenn der Mauszeiger auf ein zugehöriges Element bewegt oder es fokussiert wird. Eine Kurzinfo kann nicht direkt ausgewählt oder mit ihr interagiert werden. Kurzinfos sind kein Ersatz für Labels oder andere wichtige Informationen. Nutzer sollten ihre Aufgabe auch ohne Kurzinfo erledigen können.
Ein-/Ausblenden-Tipp und Kurzinfo
Wie bei vielen anderen Komponenten gibt es unterschiedliche Beschreibungen dafür, was ein Kurzinfofeld ist, z. B. in der MDN, der WAI ARIA, bei Sarah Higley und in Inclusive Components. Mir gefällt die Trennung zwischen Kurzinfos und Ein-/Aus-Infos. Eine Kurzinfo sollte nicht interaktive Zusatzinformationen enthalten, während eine Ein-/Aus-Schaltfläche interaktive und wichtige Informationen enthalten kann. Der Hauptgrund für die Unterschiede ist die Barrierefreiheit: Wie sollen Nutzer zum Pop-up gelangen und auf die darin enthaltenen Informationen und Schaltflächen zugreifen? Ein-/Aus-Tipps werden schnell komplex.
Hier ist ein Video eines Toggle-Tips von der Website Designcember. Es zeigt ein Overlay mit Interaktivität, das ein Nutzer anpinnen, öffnen und erkunden kann und dann mit einem leichten Tippen oder der Escape-Taste schließt:
Bei dieser GUI-Herausforderung ging es um eine Kurzinfo, bei der fast alles mit CSS erledigt werden sollte. Hier ist die Anleitung zum Erstellen.
Markieren & Zeichnen
Ich habe mich für ein benutzerdefiniertes Element <tool-tip>
entschieden. Autoren müssen benutzerdefinierte Elemente nicht in Webkomponenten umwandeln, wenn sie das nicht möchten. Der Browser behandelt <foo-bar>
genau wie eine <div>
. Sie können sich ein benutzerdefiniertes Element wie eine Klassenname mit weniger Spezifität vorstellen. Es wird kein JavaScript verwendet.
<tool-tip>A tooltip</tool-tip>
Das ist wie ein Div-Element mit Text. Wir können den Baum für die Barrierefreiheit von kompatiblen Screenreadern durch Hinzufügen von [role="tooltip"]
verknüpfen.
<tool-tip role="tooltip">A tooltip</tool-tip>
Für Screenreader wird es jetzt als Kurzinfo erkannt. Im folgenden Beispiel sehen Sie, dass das erste Linkelement ein erkanntes Kurzinfoelement im Baum hat, das zweite jedoch nicht. Der zweite Nutzer hat diese Rolle nicht. Im Abschnitt „Styles“ werden wir diese Strukturansicht verbessern.
Als Nächstes müssen wir dafür sorgen, dass die Kurzinfo nicht fokussiert werden kann. Wenn ein Screenreader die Rolle der Kurzinfo nicht versteht, können Nutzer den Fokus auf das <tool-tip>
legen, um den Inhalt zu lesen. Das ist nicht erforderlich. Screenreader fügen den Inhalt dem übergeordneten Element hinzu. Daher muss es nicht fokussiert werden, um barrierefrei zu sein. Hier können wir inert
verwenden, um zu verhindern, dass Nutzer diesen Tooltipp versehentlich in ihrem Tab-Navigationsfluss sehen:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
Ich habe dann Attribute als Schnittstelle verwendet, um die Position der Kurzinfo anzugeben. Standardmäßig wird für alle <tool-tip>
die Position „oben“ festgelegt. Die Position kann jedoch für ein Element angepasst werden, indem tip-position
hinzugefügt wird:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
Ich verwende für solche Fälle in der Regel Attribute anstelle von Klassen, damit dem <tool-tip>
nicht gleichzeitig mehrere Positionen zugewiesen werden können.
Es kann nur eine oder keine geben.
Platzieren Sie abschließend <tool-tip>
-Elemente in dem Element, für das Sie eine Kurzinfo angeben möchten. Hier teile ich den alt
-Text mit sehenden Nutzern, indem ich ein Bild und ein <tool-tip>
in ein <picture>
-Element platziere:
<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>
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>
Bedienungshilfen
Da ich mich für Tooltips und nicht für Toggletips entschieden habe, ist dieser Abschnitt viel einfacher. Zuerst möchte ich die gewünschte Nutzererfahrung skizzieren:
- Blenden Sie zusätzliche Meldungen in kleinen Bereichen oder überladenen Benutzeroberflächen aus.
- Wenn ein Nutzer den Mauszeiger auf ein Element bewegt, den Fokus darauf legt oder per Touchscreen mit einem Element interagiert, wird die Nachricht angezeigt.
- Wenn Sie den Mauszeiger bewegen, den Fokus auf die Nachricht legen oder sie berühren, wird sie wieder ausgeblendet.
- Achten Sie außerdem darauf, dass alle Bewegungen reduziert werden, wenn ein Nutzer eine Einstellung für reduzierte Bewegungen festgelegt hat.
Unser Ziel ist es, auf Nachfrage zusätzliche Informationen zu senden. Nutzer mit Sehvermögen, die eine Maus oder Tastatur verwenden, können den Mauszeiger auf die Nachricht bewegen, um sie zu sehen. Ein sehbehinderter Nutzer eines Screenreaders kann den Fokus auf die Nachricht legen, um sie zu sehen und über sein Tool akustisch zu erhalten.
Im vorherigen Abschnitt haben wir den Baum für die Barrierefreiheit, die Rolle „Tooltip“ und „inert“ behandelt. Jetzt müssen wir nur noch testen und prüfen, ob die Nutzer die Tooltip-Nachricht richtig sehen. Beim Testen ist nicht klar, welcher Teil der akustischen Nachricht eine Kurzinfo ist. Das ist auch beim Debuggen im Baum für Barrierefreiheit zu sehen: Der Linktext „top“ wird ohne Unterbrechung mit „Look, tooltips!“ zusammengeführt. Der Screenreader bricht den Text nicht und erkennt ihn nicht als Kurzinfoinhalt.
Fügen Sie dem <tool-tip>
ein Pseudo-Element hinzu, das nur für Screenreader sichtbar ist, und wir können unseren eigenen Prompt-Text für sehbehinderte Nutzer 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 den aktualisierten Navigationsbaum für Barrierefreiheit. Er enthält jetzt nach dem Linktext einen Semikolon und einen Prompt für die Kurzinfo „Hat Kurzinfo:“.
Wenn ein Screenreader-Nutzer den Fokus auf den Link legt, wird jetzt „oben“ angesagt, es folgt eine kurze Pause und dann „hat Kurzinfo: siehe Kurzinfos“. Dies gibt Nutzern von Screenreadern einige nützliche UX-Tipps. Die Verzögerung sorgt für eine schöne Trennung zwischen dem Linktext und der Kurzinfo. Wenn „hat Kurzinfo“ angekündigt wird, kann ein Screenreader-Nutzer sie ganz einfach abbrechen, wenn er sie bereits gehört hat. Das erinnert sehr an das schnelle Bewegen des Mauszeigers über ein Element und das schnelle Entfernen des Mauszeigers, wie Sie es bereits bei der zusätzlichen Nachricht gesehen haben. Das war eine gute UX-Parität.
Stile
Das <tool-tip>
-Element ist ein untergeordnetes Element des Elements, für das es zusätzliche Informationen darstellt. Beginnen wir also mit den Grundlagen für den Overlay-Effekt. So nehmen Sie ein Dokument aus dem Dokumentenfluss heraus:position absolute
tool-tip {
position: absolute;
z-index: 1;
}
Wenn das übergeordnete Element kein Stapelkontext ist, wird die Kurzinfo an dem nächsten Element platziert, das ein Stapelkontext ist. Das ist nicht das gewünschte Verhalten. Im Block gibt es eine neue Auswahl, die Ihnen helfen kann: :has()
:
:has(> tool-tip) {
position: relative;
}
Machen Sie sich nicht zu viele Sorgen um die Browserunterstützung. Denken Sie daran, dass diese Kurzinfos nur ergänzend sind. Wenn sie nicht funktionieren, ist das kein Problem. Zweitens: Im JavaScript-Abschnitt stellen wir ein Script bereit, um die Funktionen zu polyfillen, die wir für Browser ohne :has()
-Unterstützung benötigen.
Als Nächstes machen wir die Kurzinfos nicht interaktiv, damit sie keine Mausereignisse von ihrem übergeordneten Element stehlen:
tool-tip {
…
pointer-events: none;
user-select: none;
}
Verbergen Sie dann die Kurzinfo mithilfe der Deckkraft, damit wir sie mit einem Crossfade einblenden können:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
und :has()
erledigen hier die meiste Arbeit. Sie informieren tool-tip
, das übergeordnete Elemente enthält, über die Nutzerinteraktion, um die Sichtbarkeit einer untergeordneten Kurzinfo umzuschalten. Mausnutzer können den Mauszeiger bewegen, Tastatur- und Screenreadernutzer können den Fokus setzen und Touchnutzer können tippen.
Da das Overlay zum Ein- und Ausblenden für sehbehinderte Nutzer funktioniert, ist es an der Zeit, einige Stile für die Farbgebung, Positionierung und das Dreieck hinzuzufügen. Bei den folgenden Stilen werden benutzerdefinierte Eigenschaften verwendet, die auf dem bisherigen Stand aufbauen, aber auch Schatten, Typografie und Farben hinzufügen, damit es wie eine schwebende Kurzinfo aussieht:
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
Für die Kurzinfo müssen nur wenige Farben verwaltet werden, da die Textfarbe über das System-Keyword CanvasText
von der Seite übernommen wird. Da wir benutzerdefinierte Properties zum Speichern der Werte erstellt haben, können wir nur diese benutzerdefinierten Properties aktualisieren und das Theme den Rest erledigen lassen:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
Für das helle Design passen wir den Hintergrund an Weiß an und machen die Schatten viel weniger stark, indem wir ihre Deckkraft anpassen.
Von rechts nach links
Um Lesemodi von rechts nach links zu unterstützen, wird in einer benutzerdefinierten Eigenschaft der Wert der Dokumentausrichtung als -1 oder 1 gespeichert.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
So können Sie die Positionierung der Kurzinfo unterstützen:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
Außerdem wird angezeigt, wo sich das Dreieck befindet:
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
Außerdem kann er für logische Transformationen für translateX()
verwendet werden:
--_x: calc(var(--isRTL) * -3px * -1);
Position der Kurzinfo
Positionieren Sie die Kurzinfo logisch mit den Eigenschaften inset-block
oder inset-inline
, um sowohl die physische als auch die logische Position der Kurzinfo zu steuern. Der folgende Code zeigt, wie die vier Positionen sowohl für die Leserichtung von links nach rechts als auch von rechts nach links formatiert werden.
Oben und Blockanfang ausrichten
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;
}
Rechtsbündige und Inline-Ende-Ausrichtung
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
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;
}
Linksbündig und Inline-Start
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 aktiviert und deaktiviert. In diesem Abschnitt animieren wir zuerst die Deckkraft für alle Nutzer, da dies eine allgemein sichere, reduzierte Bewegungsübergang ist. Anschließend animieren wir die Transformationsposition, damit die Kurzinfo aus dem übergeordneten Element herausgeschoben wird.
Eine sichere und sinnvolle Standardüberleitung
Legen Sie für das Kurzinfo-Element einen Stil fest, um die Deckkraft zu ändern und es zu transformieren, z. B. so:
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, auf der eine Kurzinfo angezeigt werden kann, wenn der Nutzer mit Bewegung einverstanden ist, positionieren Sie die Eigenschaft „translateX“ leicht, indem Sie ihr einen kleinen Abstand geben, von dem aus sie sich bewegen soll:
@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;
}
}
Beachten Sie, dass hier der Status „out“ (Abwesend) festgelegt wird, da sich der Status „in“ (Anwesend) bei translateX(0)
befindet.
JavaScript
Meiner Meinung nach ist das JavaScript optional. Das liegt daran, dass keine dieser Kurzinfos zur Ausführung einer Aufgabe in Ihrer Benutzeroberfläche erforderlich sein sollte. Wenn die Kurzinfos also komplett fehlen, sollte das kein Problem sein. Das bedeutet auch, dass wir die Kurzinfos als fortlaufend verbessert betrachten können. Letztendlich wird :has()
von allen Browsern unterstützt und dieses Script kann dann vollständig entfernt werden.
Das polyfill-Script dient zwei Zwecken und wird nur ausgeführt, wenn der Browser :has()
nicht unterstützt. Prüfen Sie zuerst, ob :has()
unterstützt wird:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
Suchen Sie als Nächstes die übergeordneten Elemente von <tool-tip>
s und geben Sie ihnen eine Klassennamen:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
Führen Sie als Nächstes eine Reihe von Stilen ein, die diese Klassennamen verwenden, und simulieren Sie die :has()
-Auswahl für genau dasselbe 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 war's auch schon. Jetzt werden die Kurzinfos in allen Browsern angezeigt, wenn :has()
nicht unterstützt wird.
Fazit
Jetzt, da Sie wissen, wie ich es gemacht habe, wie würden Sie es machen? 🙂 Ich freue mich sehr auf die popup
API, mit der sich Ein-/Aus-Schaltflächen einfacher erstellen lassen, die Top-Ebene, die Z-Index-Probleme verhindert, und die anchor
API, mit der sich Elemente im Fenster besser positionieren lassen. Bis dahin erstelle ich Tooltips.
Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können.
Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.
Remixe der Community
Noch keine Aktivität hierzu.
Ressourcen
- Quellcode auf GitHub