Eine grundlegende Übersicht zum Erstellen eines benutzerdefinierten Elements für farbadaptierte und barrierefreie Kurzinfos.
In diesem Beitrag möchte ich meine Gedanken zum Erstellen eines farbadaptiven und barrierefreien benutzerdefinierten <tool-tip>
-Elements mit Ihnen teilen. 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 verknüpftes Element bewegt oder darauf 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. Ein Nutzer sollte seine Aufgabe ohne Kurzinfo vollständig 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 ergänzende Informationen enthalten, während eine Ein/Aus-Schaltfläche Interaktionen und wichtige Informationen enthalten kann. Der Hauptgrund für diese Kluft ist die Barrierefreiheit: Wie sollen Nutzer zum Pop-up navigieren und auf die darin enthaltenen Informationen und Schaltflächen zugreifen können? 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>
Screenreader erkennen sie jetzt als Kurzinfo. Im folgenden Beispiel sehen Sie, dass das erste Linkelement ein erkanntes Kurzinfoelement im Baum hat, das zweite jedoch nicht. Die zweite Person hat die 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 Tipp versehentlich im Tab-Navigationspfad 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>
-Elemente eine "obere" Position verwendet. Die Position eines Elements kann jedoch durch Hinzufügen von tip-position
angepasst werden:
<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 eins oder keins angegeben werden.
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 in begrenzten Bereichen oder überladenen Benutzeroberflächen zusätzliche Meldungen 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.
- Blenden Sie die Nachricht wieder aus, wenn Sie den Mauszeiger darauf bewegen, den Fokus darauf bewegen oder auf das Element tippen.
- Achten Sie außerdem darauf, dass alle Bewegungen reduziert werden, wenn ein Nutzer eine Einstellung für reduzierte Bewegungen festgelegt hat.
Unser Ziel ist eine ergänzende On-Demand-Botschaft. Sehende Nutzer mit Maus oder Tastatur 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 die Baumstruktur für Barrierefreiheit, die Rolle der Kurzinfo und den Inaktivvorgang behandelt. Jetzt müssen Sie sie testen und prüfen, ob die Nutzerfreundlichkeit die Kurzinfo-Nachricht dem Nutzer korrekt anzeigt. 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 einen 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. Sie erinnern sich sehr daran, dass Sie schnell den Mauszeiger darauf bewegen und die Maus darüber bewegen können, wie Sie bereits die ergänzende Meldung 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:
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 Gedanken über die Browserunterstützung. Denken Sie zunächst daran, dass diese
Kurzinfos ergänzend sind. Wenn sie nicht funktionieren, ist das kein Problem. Zweitens: Im JavaScript-Abschnitt stellen wir ein Script bereit, um die erforderlichen Funktionen für Browser ohne :has()
-Unterstützung zu polyfillen.
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;
}
Blenden Sie dann die Kurzinfo mit der Deckkraft aus, damit wir die Kurzinfo mit einer Überblendung überblenden können:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
und :has()
übernehmen hier den Großteil der Arbeit. Sie sorgen dafür, dass tool-tip
, die übergeordnete Elemente enthalten, auf Interaktivität des Nutzers reagiert und so die Sichtbarkeit einer untergeordneten Kurzinfo ein- und ausschaltet. 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 helfen wir 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);
}
Außerdem kann er für logische Transformationen für 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 Kurzinfo-Positionen zu verarbeiten. 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 unten und 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 wird zunächst die Deckkraft für alle Nutzer animiert, da es sich um einen allgemein sicheren Übergang mit reduzierter Bewegung handelt. Dann animieren wir die Transformationsposition, sodass die Kurzinfo aus dem übergeordneten Element herausrutscht.
Eine sichere und sinnvolle Standardüberleitung
Passen Sie das Element der Kurzinfo so an, dass Deckkraft und Transformation übergangen werden:
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 Bewegungen 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 gelesen werden müssen, um eine Aufgabe in Ihrer UI auszuführen. Wenn die Kurzinfos also komplett fehlen, sollte das kein großes 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 vollständig entfernt werden.
Das Polyfill-Skript führt zwei Dinge aus, und zwar nur, 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, wo Sie wissen, wie ich vorgegangen bin, wie würden Sie ‽ 🙂 Ich freue mich sehr auf die popup
API, um die Ein-/Aus-Schaltflächen zu erleichtern, die oberste Ebene ohne Z-Index-Konflikte und die anchor
API für eine bessere Positionierung im Fenster. 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.
Community-Remixe
Hier gibt es noch nichts zu sehen.
Ressourcen
- Quellcode auf GitHub