HTML

Přístupný accordion a rozbalovací panely

Tlačítko, které po kliknutí odkryje další obsah (ať je to jeden rozbalovací panel, nebo celý accordion), bez správné obsluhy o svém stavu čtečce nic neřekne. Uživatel klávesnice ani čtečky obrazovky pak neví, jestli je panel otevřený, ani co přesně tlačítko ovládá.

1. Nativní <details> a <summary>

Pro jeden samostatný rozbalovací panel je nejjednodušší cesta nativní dvojice <details> a <summary>. Prohlížeč sám zajišťuje klávesovou obsluhu (Enter i mezerník na summary) i správné ohlášení stavu čtečce, bez jediného řádku JavaScriptu:

<details>
    <summary>Zobrazit podrobnosti</summary>
    <p>Skrytý obsah, který se zobrazí po rozkliknutí…</p>
</details>

<details> má implicitní roli group, <summary> implicitní roli button. Čtečka ohlásí „Zobrazit podrobnosti, tlačítko, sbaleno" a po rozkliknutí „rozbaleno", stejně jako u ručně stavěné verze v dalších částech, ale bez potřeby psát role, aria-expanded nebo obsluhu kliknutí ručně.

Nevýhoda se projeví až u accordionu: summary není nadpis, takže čtečka nedovolí mezi sekcemi přeskakovat příkazem pro procházení nadpisů, jako to jde u ruční verze s <h3> obalujícím tlačítko (viz část 3). Pro jeden samostatný disclosure panel to nevadí, u víc sekcí za sebou je to kompromis, který stojí za zvážení.

2. Základní disclosure přes ARIA: jedno tlačítko, jeden panel

Tam, kde nativní <details> nestačí, třeba kvůli vlastnímu vzhledu bez výchozí šipky prohlížeče nebo kvůli potřebě řídit otevírání z JavaScriptu jinak, se disclosure staví ručně:

<button type="button" aria-expanded="false" aria-controls="podrobnosti-panel" id="podrobnosti-tlacitko">
    Zobrazit podrobnosti
</button>
<div id="podrobnosti-panel" hidden>
    <p>Skrytý obsah, který se zobrazí po rozkliknutí…</p>
</div>

A JavaScript, který stav přepíná:

const tlacitko = document.getElementById('podrobnosti-tlacitko');
const panel = document.getElementById('podrobnosti-panel');

tlacitko.addEventListener('click', () => {
    const otevreno = tlacitko.getAttribute('aria-expanded') === 'true';
    tlacitko.setAttribute('aria-expanded', String(!otevreno));
    panel.hidden = otevreno;
});

Podle W3C ARIA APG (Disclosure pattern) stačí na fokusovaném tlačítku Enter nebo mezerník. U obyčejného <button> to prohlížeč zajišťuje sám, žádný vlastní kód navíc není potřeba.

3. Accordion: víc panelů nad sebou

Accordion je jen víc disclosure prvků za sebou, kde je hlavička každého panelu navíc obalená ve vlastním nadpisu. Díky tomu jde nadpisy procházet stejně jako zbytek stránky, ne jen tabulátorem:

<h3>
    <button type="button" aria-expanded="false" aria-controls="sekce-1-panel" id="sekce-1-tlacitko">
        První sekce
    </button>
</h3>
<div id="sekce-1-panel" role="region" aria-labelledby="sekce-1-tlacitko" hidden>
    <p>Obsah první sekce…</p>
</div>

<h3>
    <button type="button" aria-expanded="false" aria-controls="sekce-2-panel" id="sekce-2-tlacitko">
        Druhá sekce
    </button>
</h3>
<div id="sekce-2-panel" role="region" aria-labelledby="sekce-2-tlacitko" hidden>
    <p>Obsah druhé sekce…</p>
</div>

Podle APG (Accordion pattern) mezi hlavičkami nejde procházet šipkami jako u tabů nebo menu, jen běžným Tabem, stejně jako zbytkem stránky. role="region" na panelu se hodí hlavně tam, kde panel obsahuje vlastní strukturu (další nadpisy, vnořený accordion). U accordionu s víc než zhruba šesti panely najednou otevřenými by region landmarky zbytečně zahltily seznam landmarků, tam se raději vynechá.

4. Jeden otevřený panel, nebo víc najednou?

APG žádnou z variant nevyžaduje ani nedoporučuje. Je to čistě rozhodnutí návrhu. Chcete-li povolit jen jeden otevřený panel najednou, při otevření jednoho zavřete ostatní ve stejném obslužném kódu kliknutí:

function otevriPanel(indexOtevriteho, vsechnyTlacitka, vsechnyPanely) {
    vsechnyTlacitka.forEach((tlacitko, i) => {
        const otevrit = i === indexOtevriteho;
        tlacitko.setAttribute('aria-expanded', String(otevrit));
        vsechnyPanely[i].hidden = !otevrit;
    });
}

Necháte-li otevřených víc panelů najednou, stačí přepínat jen ten jeden, na který uživatel klikl, přesně jako u samostatného disclosure z druhé části.

U nativní verze z první části existuje stejná možnost bez JavaScriptu. Prvky <details> se stejnou hodnotou atributu name tvoří skupinu, kde jde otevřít vždy jen jeden najednou, protože otevření jednoho automaticky zavře ostatní se stejným jménem:

<details name="nastaveni">
    <summary>První sekce</summary>
    <p>Obsah první sekce…</p>
</details>
<details name="nastaveni">
    <summary>Druhá sekce</summary>
    <p>Obsah druhé sekce…</p>
</details>

Podporují to všechny aktuální prohlížeče, u starších verzí se name prostě ignoruje a panely pak fungují nezávisle na sobě, což je bezpečný způsob degradace.

Kde se to na tomto webu používá

Hlavní navigace webu používá přesně nativní <details>/<summary> z první části. Rozbalovací položky menu v hlavičce, třeba dropdown „Články" nebo „Znalostní báze", jsou obyčejné <details class="main-nav__dropdown"> se <summary> uvnitř, bez jediného řádku vlastního JavaScriptu na otevírání a zavírání.

Proč to takhle je

  • aria-expanded patří na tlačítko, ne na panel. Čtečka potřebuje vědět, v jakém stavu je ovládací prvek, ne panel samotný.
  • Nadpis (h2/h3) obalující tlačítko u accordionu umožňuje procházet sekce stejně jako kterýkoliv jiný nadpis na stránce, ne jen lineárně tabulátorem.
  • aria-controls propojení tlačítka s panelem sice řada čteček neohlásí, ale zůstává zdokumentovaný vztah pro nástroje testování a je součástí specifikace. Nespoléhat se na něj jako na jedinou vazbu, ale nevynechávat ho.
  • Nativní <details>/<summary> odbourává celou tuhle práci u jednoho disclosure panelu, protože sémantiku i klávesovou obsluhu zajišťuje prohlížeč sám. Vlastní ruční řešení dává smysl tam, kde je potřeba procházení accordionu po nadpisech, vlastní vzhled bez výchozí šipky nebo řízení otevírání z kódu nad rámec toho, co nabízí name.

Pravidla

  • WCAG 4.1.2 (Název, role, hodnota) — aria-expanded musí vždy odpovídat skutečnému stavu panelu.
  • WCAG 1.3.1 (Informace a vztahy) — vztah tlačítko/panel (aria-controls) a hlavička/nadpis u accordionu.
  • WCAG 2.1.1 (Klávesnice) — celé ovládání funguje čistě klávesnicí (Enter/mezerník na tlačítku, Tab mezi hlavičkami).

To je celé: nativní <details>/<summary> stačí na jeden panel bez vlastního kódu, ruční disclosure je jedno tlačítko a jeden panel, accordion jen víc takových dvojic za sebou s nadpisy navíc.

Zdroje

Štítky