Infobox/Akkordeon mit details

Aus SELFHTML-Wiki
< Infobox(Weitergeleitet von Accordion)
Wechseln zu: Navigation, Suche

Ein disclosure widget ist ein UI-Element, das es ermöglicht, Seiteninhalte zu verstecken und mit einem Klick auf eine Zusammenfassung oder eine Überschrift zusammen mit einem Indikator (manchmal ein Dreieck oder ein Pluszeichen) zusätzliche Inhalte anzeigt.

Dieses Verhalten wird oft als „Akkordeon“ bezeichnet, vor allem wenn mehrere solcher aufklappbaren Elemente vorhanden sind. Einige Akkordeons sind exklusive Akkordeons, was bedeutet, dass höchstens eine der Angaben in diesem Akkordeon gleichzeitig geöffnet sein kann.

Früher konnte ein solches Akkordeon nur mit vielen Zeilen JavaScript verwirklicht werden. Dabei wurde jedoch oft auf eine Bedienbarkeit mit der Tastatur keine Rücksicht genommen. Das details-Element bietet nun eine zugängliche und einfache Alternative.

Offenlegungswidget mit details

Das details-Element bezeichnet einen semantischen Abschnitt, der mit Text, Bildern (oder einem figure-Element, einem Formular oder auch anderen details) gefüllt werden kann. Mit ihm kann man Seiteninhalte verstecken und mit einem Klick auf summary aufklappen.

Überschrift mit summary

Mit summary können Sie dem Akkordeon eine Überschrift geben. Links daneben wird ein details-marker-Pseudoelement in Form eines schwarzen Dreiecks angezeigt. Es verändert bei einer Zustandsänderung seine Richtung.

Beachten Sie: Wenn kein Summary-Element verwendet wird, zeigt der Browser den Text Details an.


details und summary ansehen …
<details> <summary> Update News: </summary> <p> Das details-Element ermöglicht es, ganz ohne den Einsatz von JavaScript, ursprünglich verborgene Textinhalte aufzuklappen. </p> </details>


Ein Klick mit der Maus öffnet den versteckten Abschnitt. Alternativ bringt die Tab-Taste die Überschrift summary in den Fokus. Ein Klick auf Leertaste oder Enter öffnet dann den Text.

open

Mit dem Klick auf die summary wird details das Attribut open angehängt. Um die Details direkt anzuzeigen, können Sie es auch direkt im HTML-Code notieren. Es eignet sich auch als Angriffspunkt, um das betreffende Element mit CSS zu gestalten:

Einbindung von open
<details open> 
  <summary>
    Browser-News:
  </summary>
  <p>
    Während Chrome, Opera und Safari das neue Element schon sehr lange unterstützen, 
    ist es in Firefox seit Version 49 verfügbar.
  </p>
</details>


CSS-Formatierung für open
details[open] {
  background: coral;
  margin-bottom: 1em;
}

Gestaltung mit CSS

Wie das details-Element auf Ihrer Webseite dann aussieht, bleibt Ihnen und Ihrer Fantasie überlassen.

Formatierung des details-markers

Das Summary Element enthält ein kleines schwarzes Dreieck.

Wenn Sie ein summary-Element im Seiteninspektor überprüfen, entdecken Sie weitere, „versteckte“ Elemente im Shadow DOM:

Details-shadow-root.png

In CSS3 kann der details-marker mit dem ::marker-Selektor ausgewählt und bedingt formatiert werden.[1]

Die Spezifikation nennt für die Marker-Box die Eigenschaften content sowie sämtliche animation- und transition-Eigenschaften. Hinzu kommen text-combine-upright, unicode-bidi und direction. Der Marker-Inhalt kann durch alle font-Eigenschaften sowie white-space, text-transform, letter-spacing und color beeinflusst werden.

CSS-Formatierung des details-Symbols ansehen …
summary::-webkit-details-marker,	
summary::marker {
  content:  " 🡳 "; /* Verwendung des "Pfeil"-Symbols anstelle des Dreiecks */
  color: green;
  font-size: 2em;
  font-weight: bold;
  transition: all 0.5s;  
}	

details[open] summary::-webkit-details-marker,
details[open] summary::marker {
  content:  " 🡱 ";
  color: red;
}
Beachten Sie: Dieses Beispiel funktioniert (Stand: Oktober 2023) in den Chromium-Browsern und im Firefox. Im Safari 17 wird der marker zwar grün und rot eingefärbt; als Symbol wird aber weiterhin das Dreieck und nicht der in content zugewiesene Pfeil verwendet.

Ersatz durch Pseudoelement

Wenn man zum Stylen des Markers Eigenschaften benutzen will, die von den Browsern noch nicht dafür vorgesehen sind, kann man ihn stattdessen auch ausblenden und dafür ein eigenes Pseudoelement erzeugen und formatieren:

CSS-Ersatz durch Pseudoelement ansehen …
summary {
   position: relative;  
   }

summary::marker, summary::-webkit-details-marker {
   color: transparent;
}

summary::after {
   content:  "+"; 
   position: absolute;
   color: green;
   font-size: 2em;
   font-weight: bold; 
   right: 1em;
   top: .2em;
   transition: all 0.5s;
} 

details[open] summary::after {
 color: red;
 transform: translate(5px,0) rotate(45deg);
}

Die display: Eigenschaft gehört zu denen, die vom Marker nicht beachtet werden, deshalb wird einfach die Farbe auf transparent gesetzt. Safari unterstützt (Stand 12/2023) ::marker noch nicht und benötigt ::-webkit-details-marker.

Das ::after-Pseudoelement erhält eine absolute Positionierung am rechten Rand. Über die content-Eigenschaft erhält es ein Plus-Zeichen, das auf zusätzlichen Inhalt verweist.

Bei einer Änderung des open-Attributs wird es mit einem weichen Übergang um 45° gedreht und so zu einem X, das das Fenster wieder schließt.

Tooltip mit details

Ein Tooltip ist ein kleines Pop-up-Fenster in Anwendungsprogrammen oder Webseiten. Oft wird es mit einem abbr-Element realisiert, dessen title-Attribut dann ausgelesen wird. Mit details geht es einfacher:

Tooltipps ansehen …
[role=button] {
	background: var(--accent);
	color: var(--background);
	border-radius: 0 0.5em 0.5em;
	border: thin solid var(--accent);
	width: min-content;
	padding: 0 1.5em 0.5em;
	font-size: 1.5em;
	line-height: 100%;
	font-weight: bold;
}

details[open] [role=button] {
	background: var(--background);
	color: var(--accent);
}

details p {
	position: relative;
	padding: 1em;
	border: thin solid var(--accent);
	border-radius: 0 0.5em 0.5em;	
}

details p::before {
	content: ' ';
	position: absolute;
	width: 0;
	height: 0;
	top: -1em;
	left: 20%;
	margin-left: -1em;
	border-style: solid;
	border-width: 0 1em 1em 1em;
	border-color: transparent transparent var(--accent) transparent;
}

Das summary-Element erhält jetzt das Aussehen eines Buttons. Damit sich dieser nicht über die gesamte Breite des verfügbaren Platzes erstreckt erhält er mit width: min-content eine an den vorhandenen Inhalt angepasste Breite.

Bei einem Klick auf den (vermeintlichen) Button öffnet sich details p als Sprechblase.

Hauptartikel: Infobox/Tooltips mit Popover

Akkordeon

Ein Akkordeon ist ein sehr nützliches Entwurfsmuster für progressive disclosure (englisch für schrittweise Offenlegung).[2] – wichtige Abschnitte werden hervorgehoben und mit einem Tap oder Klick können, wenn nötig, weitere Details geöffnet werden.

HTML5 bietet mit dem details-Element eine bequeme, in allen modernen Browsern unterstützte Lösung. Allerdings gibt es derzeit keine Möglichkeit, den Übergang zwischen open und closed zu animieren, bzw. mehrere details-Elemente zu verknüpfen. In diesem Tutorial lernen Sie, wie Sie details-Akkordeons mit JavaScript so anreichern, dass sie als Fallback für alte Browser dienen können und mehr Komfort als eine reine HTML-Lösung bieten.

So bleibt das Design übersichtlich und zeigt wesentliche Informationen zuerst, während weiterführende Details leicht erreichbar sind. Oft können so unübersichtliche und gedrängt aussehende Seiten ansprechender präsentiert werden.

Wie so etwas früher aussah, kann man hier sehen:

obsoletes HTML-Markup eines Akkordeons mit aria-labels
<div id="accordion">
  <button aria-expanded="false" aria-controls="collapsible-0">Section 1</button>
  <div id="collapsible-0" aria-hidden="true">
    <p>Inhalt 1</p>
  </div>
  <button aria-expanded="false" aria-controls="collapsible-1">Section 2</button>
  <div id="collapsible-1" aria-hidden="true">
    <p>Inhalt 2</p>
  </div>
</div>

Das Akkordeon besteht aus Buttons und einem dazugehörenden div. Um diesen Zusammenhang auch für Screenreader klarzustellen, erhalten die Buttons ein aria-controls-Attribut, dass auf die id des dazugehörenden divs verweist. Jeder Button erhält ein aria-expanded-Attribut, dass den jeweils geschalteten Zustand anzeigt, was auch mit CSS sichtbar gemacht wird.


Fazit: Die aria-labels gaben den bedeutungslosen buttons und divs einen Sinn, durch das Vorlesen aller aria-Attribute wurde dieser Segen aber oft zum Fluch, da die vielen Attribute doch vom eigentlichen Inhalt ablenkten!

HTML

Es wäre schön, wenn sich das Akkordeon nicht abrupt, sondern sich mit einem weichen Übergang aufschieben würde. Leider ist dies mit CSS alleine nur eingeschränkt möglich:

Akkordeon mit CSS-animation ansehen …
<details> 
   <summary>
        immer sichtbare Überschrift
   </summary>
   <div>
        aufzuklappender Inhalt
   </div>
</details>
Das details-Element enthält eine summary und weitere, aufzuklappende Inhalte in einem div-Element.
Es erfüllt genau die Anforderungen an ein Akkordeon und ist im Gegenteil zu vielen mit jQuery vorgefertigten, aber auch rein CSS-basierten Beispielen in allen unterstützten Browsern mit seinem Standardverhalten zugänglich, d. h. auch mit der Tastatur zu öffnen und zu schließen.
details > div {
  padding: .5em;
  overflow: hidden;}

details[open] > div {
  animation: sliding 1s forwards; 
}

@keyframes sliding {
  0% {
    height: 0;
  }  

  100% {
    height: 15em;	
  }
}

Im Beispiel wird die Höhe des div-Containers aminiert. Beim Setzen des open-Attribut wird die animation sliding gestartet. Hierbei benötigt man aber einen festen Wert (hier: 15em), der bei einer Veränderung des Inhalts angepasst werden muss.

Beachten Sie: Solche Magic Numbers sollten nach Möglichkeit vermieden werden!

Um die Interaktion (weiches Öffnen des details-Elements) zu verbessern, kann man JavaScript einsetzen:

JavaScript

Die Funktionalität des Beispiels ist bereits vorhanden; mit einem Klick springt das details-Element abrupt von "zu" auf "auf" und umgekehrt. Mit JavaScript können Sie nun zusätzlichen Komfort einbauen:

weicher Übergang

Mit CSS ist eine transition der Höhe nur möglich, wenn beide Zustände feste Werte erhalten, der Wert auto für die „normale“, durch den Inhalt bestimmte Höhe bewirkt wieder ein Ausklappen.

JavaScript soll nun nur den Aufklapp-Mechanismus mit einem weichen Übergang versehen. Dabei soll die Höhe des im details umschlossenen Containers automatisch ermittelt werden.

Ermittlung der Höhe ansehen …
const detailElements = document.querySelectorAll("details"),
style = document.createElement("style");
let stylecontent = "";
detailElements.forEach(function(detailElement,index){
  stylecontent += 
  `details[open]:nth-of-type(${index+1})height:${detailElement.getBoundingClientRect().height}px;}`;
  detailElement.removeAttribute("open");
  stylecontent += 
  `details:nth-of-type(${index+1}){height:${detailElement.getBoundingClientRect().height}px;}`;
})
style.innerText = stylecontent;
document.head.appendChild(style);

Über querySelectorAll werden alle details-Elemente selektiert und einer node list mit Namen detailElements geschrieben.

Zusätzlich wird ein style-Element erzeugt. Mit forEach wird für jedes details-Element der Liste mit getBoundingClientRect() die Höhe der Box einmal im geöffneten und einmal im geschlossenen Zustand ermittelt und jeweils in einem template-literal als CSS-Regelsatz der Variable stylecontent zugewiesen:

`details[open]:nth-of-type(${index+1}){height:${detailElement.getBoundingClientRect().height}px;}`;

CSS-Selektor ist der Elementselektor details mit dem Attribut-Selektor [open] und dann dem nth-of-type-Selektor, der mit index+1 die entsprechende Stelle im DOM erhält.

Diese Anweisungen werden mit innerText in das style-Element geschrieben und dies mit appendChild in den Elementbaum eingehängt.

Beim Öffnen und Schließen können nun die festen Pixel-Werte für die Animation verwendet werden.

Exklusives Akkordeon

Ein exklusives Akkordeon ist ein UI-Element, in dem mehrere Aufklappelemente so verbunden sind, dass höchstens eine der Angaben in diesem Akkordeon gleichzeitig geöffnet sein kann. Optimal wäre es wenn ein solches Exklusives Akkordeon nicht mit JavaScript programmiert, sondern bereits in nativem HTML vorhanden wäre.

Ein Vorschlag der open-ui.org lautet, mehrere details-Elemente über ein gemeinsames name-Attribut (wie bei Radio-Buttons) zu verbinden, liegt bei der WHATWG und wird bereits von mehreren Browsern unterstützt.[7] Mehrere details-Elemente mit demselben name-Wert bilden eine semantische Gruppe und verhalten sich wie ein exklusives Akkordeon.

beim Öffnen andere Elemente schließen ansehen …
	<details name="acc" >
		<summary> aufklappbar - Wie funktioniert das? </summary>
		<div class="content">
		</div>
	</details>
	<details name="acc">
		<summary> Sind eigene Titel-Texte möglich? </summary>
		<div class="content">
		</div>
	</details>
	<details name="acc" open>
		<summary> Exklusives Akkordeon </summary>
		<div class="content">
		</div>
	</details>

In diesem Beispiel werden drei details-Elemente mit einem gemeinsamen name-Attribut gruppiert. Bei einem Klick auf ein summary-Element wird das betreffende details-Element geöffnet, alle anderen details-Elemente geschlossen.

Dies funktioniert (Stand: Dezember 2023) bereits in Chrome, Edge und Safari. In älteren Browsern bleiben die anderen details-Elemente aufgeklappt - graceful degradation, bzw.progressive enhancement für Browser, die dies bereits unterstützen.

Registerkarten

Unter dynamischen Tabs (engl. Tabbed interfaces oder Tab Panels) versteht man das Präsentieren von Inhalten in Registerkarten. Reiter wie in einer Hängeregistratur verweisen auf die vorhandenen Registerkarten. Da nur die ausgewählte Registerkarte angezeigt wird, ist dies eine platzsparende, übersichtliche und visuell intuitive Art Inhalte zu präsentieren.


Aufbauend auf dem exklusiven Akkordeon können wir nun Registerkarten bauen, die den Inhalt bei einem Klick auf den jeweiligen Reiter anzeigen:

HTML

obsoletes HTML-Markup vor 2023 - inhaltsleere divs und ARIA-Attribute
  <ul role="tablist" id="tablist">
    <li id="link1" role="tab" aria-controls="panel-1" aria-selected="true">1 - Button</li>
    <li id="link2" role="tab" aria-controls="panel-2" aria-selected="false">2 - Button</li>
    <li id="link3" role="tab" aria-controls="panel-3" aria-selected="false">3 - Button</li>
  </ul>
 
  <div id="tabcontent">
    <div id="panel-1" role="tabpanel" aria-labelledby="link1" aria-hidden="false">
      <h3>Inhalt 1</h3>
      <p> Hier kommt was hin!</p>
    </div>
    <div id="panel-2" role="tabpanel" aria-labelledby="link2" aria-hidden="true">
    <h3>Inhalt 2</h3>
      <p> Hier kommt was hin!</p>
    </div>
    <div id="panel-3" role="tabpanel" aria-labelledby="link3" aria-hidden="true">
      <h3>Inhalt 3</h3>
      <p> Hier kommt was hin!</p>
    </div>
  </div>
</div>  
</section>

Früher bestand ein Tab Panel aus einer Navigation in Form einer Liste und einem div mit drei Kindelementen, die über aria-labelledby-Attribute mit der id der Buttons verbunden waren. Deren aria-controls-Attribut zielte auf die id der Listenelemente.

Elemente, die Aussehen und Darstellung ändern, haben im Allgemeinen Zustände, die durch Benutzeraktionen geändert werden. So ändern die einzelnen Tabs ihren Zustand von „nicht ausgewählt“ auf „ausgewählt“, wenn sie geklickt werden und werden plötzlich sichtbar. Dies kann mit den Aria-Attributen aria-selected und aria-hidden ausgezeichnet und durch CSS-Selektoren sichtbar gemacht werden.

Die Steuerungselemente waren funktionslose Listenelemente, denen erst mit JavaScript die Möglichkeit zur Benutzerinteraktion hinzugefügt wurde.


Heute verwenden wir nur unser exklusives Akkordeon, deren details-Elemente Semantik und Rollen bereits mitbringen:

Registerkarten: 3 details-Elemente in einem div ansehen …
<div class="tabs">
	<details name="tab" >
		<summary> Registerkarten</summary>
		<div class="content">
		</div>
	</details>
	<details name="tab">
		<summary> Standardverhalten</summary>
		<div class="content">
		</div>
	</details>
	<details name="tab" open>
		<summary> Barrierefreiheit </summary>
		<div class="content">
		</div>
	</details>
</div>

Die drei details-Elemente sind in einem Container mit der Klasse tabs umschlossen. Über den gemeinsamen Namen tab wird in modernen Browsern die Funktionalität des gegenseitigen Zuklappens ermöglicht.

CSS

Nun wird es Zeit, das Tab Panel mit dem gewünschten CSS zu gestalten.

CSS: absolute Positionierung übereinander ansehen …
.tabs details {
	display: inline-block;
}

.tabs summary {
	background-color: lightgrey;
	border: thin solid black;
	border-bottom: none;
	border-radius: 0.5em 0.5em 0 0;
	font-weight: bold;
	height: 1.5em;
	width: 10em;
	padding: 0.5em 0.5em 10px;
}

.tabs .content {
	position: absolute;
	left: 0;
	right: 0;
	background: white;
	border: thin solid #ccc;
	padding: .5em;
}

Funktionalität mit JavaScript

Da sich die Inhalte der einzelnen Registerkarten überdecken, ist es zwingend notwendig, einen Fallback einzubauen. In Browsern, die die Verknüpfung der details-Elemente noch nicht kennen, wird die Interaktivität mit JavaScript nachgebaut:


Fallback für ältere Browser ansehen …
document.querySelectorAll('.tabs details')
        .forEach(details => details.addEventListener('toggle', detailsToggler));

function detailsToggler(event) {
   if (!event.target.open) return;
   let container = event.target.closest('.tabs');
   for (let details of container.querySelectorAll('details')) {
      if (details != event.target)
         details.open = false;
   }
}

Mit document.querySelectorAll('…') werden alle Elemente, die zum angegebenen CSS Selektor passen, ausgewählt. In diesem Fall details-Elemente, die ein Element mit class="tabs" als Elternelement haben.

Das Ergebnis von querySelectorAll ist eine NodeList. forEach ruft die Funktion, die da in den Klammern steht, für jeden Eintrag in der NodeList – also für jedes gefundene details-Element, einmal auf.

details => … ist eine Pfeilfunktion und eine Kurzschreibweise für function(details) { return … }. Für jedes details-Element wird addEventListener aufgerufen, um für das toggle-Event die Funktion detailsToggler als Eventhandler zu registrieren.

Die Funktion detailsToggler sucht mittels der closest-Methode das nächstliegende Elternelement, auf das der angegebene CSS Selektor zutrifft. Das ist das div class="tabs". Und darauf wird wiederum mit querySelectorAll die NodeList der enthaltenen details-Elemente ermittelt.

Diese wird nun mit der For...of-Schleife durchlaufen. Nun wird geprüft, ob das gefundene details-Element mit dem Event-Target übereinstimmt. Nur, wenn das nicht der Fall ist, wird es geschlossen. Andernfalls würde das frisch geöffnete details ebenfalls gleich wieder zugemacht.

Siehe auch

Quellen

  1. MDN: ::marker
  2. smashing magazine: Designing The Perfect Accordion
    The Barebones Of An Accordion
    sehr ausführlicher Grundlagenartikel, der Anwendungsfälle und besonders die Gestaltung des markers mit vielen Beispielen zeigt.
  3. Dave Rupert: Why <details> is Not an Accordion
    tl;dr - <summary> is a button and buttons eat semantics
  4. W3C: details
  5. W3C: ARIA specs
  6. hasellinclusion: Accessible accordions part 2 – using <details> and <summary>
    Sehr ausführlicher Artikel, der die Zugänglichkeit in allen Browsern und Readern testet.
    tl;dr - In NVDA werden state changes nicht vorgelesen. (https://github.com/nvaccess/nvda/issues/8631)
  7. Add name attribute for grouping details elements into an exclusive accordion (github.com/whatwg)