
GR8 Days 2025: Dva dny, které nás opět spojily

Každá webová stránka by měla být navržena tak, aby byla přístupná všem, od zkušených uživatelů až po osoby využívající asistenční technologie. Abychom toho mohli dosáhnout konzistentně a bez kompromisů, potřebujeme jasně definovaná pravidla.
Minimální standard přístupnosti slouží jako oficiální podklad pro validaci návrhů designu, checklist při code review a základ pro testovací scénáře. Díky němu dokážeme vytvářet weby, které jsou přístupné a pohodlné pro všechny uživatele.
📚 Velmi dobře zpracované materiály o všech pravidlech specifikace WCAG najdete na webové stránce: WCAG in Plain English.
HTML obsahuje řadu tagů, které sémanticky popisují obsah na webu. Pokud jej používáme správně, např. pro navigaci použijeme <nav>
, pro každý vstup vyplníme <label>
nebo interaktivní prvky zapíšeme jako <button>
, budou čtečky obrazovky lépe rozumět obsahu a struktuře webu.
Tip: Vygenerujte si sémantické HTML pomocí AI (doporučuji např. Gemini PRO)
„Vygeneruj mi z tohoto screenshotu přístupný HTML bez stylů“
Gemini vygenerovalo:
<section aria-labelledby="benu-nadpis">
<div>
<img src="cesta-k-vasemu-obrazku.png" alt="Aplikace Benu lekárny zobrazená na displeji mobilního telefonu.">
</div>
<div>
<h2 id="benu-nadpis">
Creating a state-of-the-art experience for Benu pharmacies
</h2>
<dl>
<div>
<dt>10K+</dt>
<dd>Average daily users</dd>
</div>
<div>
<dt>99.9%</dt>
<dd>Cash free rate</dd>
</div>
<div>
<dt>4.8*</dt>
<dd>App rating on Google Play Store & App Store</dd>
</div>
</dl>
<a href="/case-study/benu-pharmacies" aria-label="Read more about the Benu pharmacies case study">
Read more
</a>
</div>
</section>
<section>
, <h2>
, <dl>
dává obsahu logickou strukturu.alt
u značky <img>
zajišťuje, že informace z obrázku jsou dostupné i pro nevidomé.aria-labelledby
pomáhá uživatelům asistenčních technologií rychle se zorientovat v obsahu stránky.aria-label
u odkazu <a>
poskytuje jednoznačný popis cíle odkazu, což je klíčové pro odkazy s obecným textem jako „Read more“.<ul>
(neuspořádaný seznam) je dobrý, <dl>
(seznam definic) je ještě přesnější. Přímo komunikuje vztah mezi hodnotou metriky (<dt>
) a jejím popisem (<dd>
), což asistenční technologie dokážou interpretovat a lépe prezentovat uživateli.ℹ️ Kromě toho sémantické HTML pomáhá vyhledávačům lépe porozumět obsahu stránky, což má výrazný vliv i na pozici ve vyhledávačích.
Proč je důležité používat správné HTML tagy, si můžeme ukázat na tlačítku:
▶️ CodeSandbox příklad ◀️
<!-- Špatný příklad ❌-->
<div className="button" onClick={handleClick}>
Open something
</div>
<!-- Dobrý příklad ✅ (onClick může být použit pouze na focusable elementech) -->
<button className="button" onClick={handleClick}>
Open something
</button>
Na první pohled tlačítko funguje. Když kliknu myší, vyvolá se funkce handleClick
stejně jako pro div
, tak i pro button
. Pokud se však na webu pohybuji pomocí klávesnice (kombinace Tab
a Enter
), div
se stává téměř nepoužitelným. Pro nativní tag <button>
prohlížeč automaticky doplnil potřebnou funkcionalitu, např. tabindex nebo focus stav.
📚 Velmi kvalitně zpracovaný materiál o všech HTML tágách je k dispozici na MDN Web Docs.
ARIA (Accessible Rich Internet Applications) je sada speciálních atributů, které můžete přidat do HTML kódu. Tyto atributy používají výhradně asistenční technologie a nijak neovlivňují vykreslený obsah na webu, ale pouze poskytují doplňující informace pro asistenční technologie.
⚠️ Používejte atributy ARIA s rozvahou a pouze jako poslední možnost, pokud neexistuje žádná alternativa v HTML! Pokud použijete atributy ARIA nesprávně, můžete tím přístupnost webu zhoršit ještě více, než kdybyste je nepoužili vůbec.
<div>
, toto je navigace
nebo tlačítko
.“role="navigation"
, role="dialog"
, role="button"
, role="search"
.<nav>
nebo <button>
, utomaticky přidávají roli, a proto je třeba používat správné HTML tagy! Nejlepší ARIA je žádná ARIA.role="tab"
, role="slider"
, ssamotná role nepřidá žádnou funkčnost. Pouze informuje čtečku obrazovky. Vaším úkolem zůstává doprogramovat ovládání klávesnicí (např. šipkami, Esc, Enter) pomocí JavaScriptu.❌ Špatně: <div role="button" tabindex="0" onclick="maFunkce()">Uložit</div>
✅ Správne: <button onclick="maFunkce()">Uložit</button>
aria-labelledby="id-popisu"
(říká, kde najít textový popis), aria-required="true"
(označuje povinné políčko), aria-invalid="true"
(označuje, že hodnota je nesprávná).aria-expanded="false"
(označuje, že rozbaľovací prvek je zabalený), aria-hidden="true"
(skryje prvek před čtečkou), aria-disabled="true"
(označuje, že prvek je neaktivní).📚 Více informací o správném používání ARIA najdete v dokumentaci MDN.
⚠️ Standardní HTML tagy obvykle nevyžadují implementaci atributů ARIA, ale vlastní řešení vyžadují zvýšenou pozornost. Obvykle pro nestandardní funkce, např. dialog, swiper, datepicker nebo další, používáme knihovny třetích stran. Ujistěte se, že tyto knihovny implementují správné atributy ARIA.
Každá HTML stránka musí mít v sekci <head>
unikátní a popisný element <title>
. Dobrý název je jedinečný, stručný a výstižný. Držte se následujících doporučení:
Specifický název stránky | Název webu
.lang="cs"
). To umožňuje asistenčním technologiím, jako jsou čtečky obrazovky, správně interpretovat a vyslovovat text.lang
pomocí JavaScriptulang
atributu.<!DOCTYPE html>
<html lang="cs"> ✅ Vyplněný lang atribut v html tagu
<head>
<meta charset="UTF-8">
<title>Webdesign na míru | Název Firmy</title> ✅ Vyplněný title
</head>
<body>
<h1>Naše služby v oblasti webdesignu</h1>
<p>Nabízíme moderní a přístupné webové stránky...</p>
<blockquote lang="en"> ✅ Pokud je text v jiném jazyce než celá stránka
<p>The only way to do great work is to love what you do.</p>
<footer>- Steve Jobs</footer>
</blockquote>
</body>
</html>
Ne všichni používají myš k navigaci na webu. Někteří nemohou myš používat kvůli nemoci, zkušení uživatelé ovládají web pomocí klávesnice nebo na televizi ovládáme web pomocí dálkového ovladače... Tito uživatelé se spoléhají na to, že všechny interaktivní prvky na webu, např. tlačítka, odkazy nebo formuláře, mají svůj focus stav.
Asi by se vám nelíbilo, kdyby se na webu nezobrazoval kurzor. Často to však děláme pro uživatele, kteří se na webu orientují pomocí klávesnice.
body {
cursor: none; /* tohle byste na webu určitě neudělali */
}
:focus {
outline: none; /* takže, prosím, nedělejte ani toto */
}
:focus
má však jednu vlastnost, která nutí vývojáře outline
mazat. Při kliknutí myší, např. na tlačítko, se kolem něj zobrazuje rámeček, což nevypadá dobře. V moderních prohlížečích můžeme tento problém snadno vyřešit, pokud místo :focus
použijeme :focus-visible
. Tento selektor již podporují všechny moderní prohlížeče a díky němu se rámeček zobrazuje pouze při navigaci pomocí klávesnice (zaměřujeme se na tlačítko pomocí klávesy Tab
).
Je třeba dbát na to, aby byl focus stav dostatečně vizuálně odlišen:
2px
tlustý a musí mít kontrast 3:1 (úroveň AAA).Mezi zaostřeným a nezaostřeným stavem musí být rozdíl v barevném kontrastu alespoň 3:1 (úroveň AAA).
Focus je alespoň 2px tlustý a musí mít kontrast 3:1 oproti nezaostřenému stavu (úroveň AAA).
// Príklad ako si vieme pridať vlastný štýl pre focus stav
button:focus-visible {
outline: 3px solid deepskyblue;
outline-offset: 3px;
}
Interaktivní prvky, např. tlačítka, pole formulářů nebo odkazy, jsou tabulovatelné a není třeba nic dodatečně implementovat. Pokud toho chceme dosáhnout např. pro <div>
můžeme mu přiřadit <div tabindex="0">
. Tuto možnost můžeme využít např. v interaktivních grafech, které v HTML nemají reprezentativní tag. Pokud chceme dosáhnout opačného efektu, tedy vypnout interaktivitu (nemůžeme označit prvek pomocí klávesy Tab
) použijeme tabindex="-1"
. Pro tabindex
bychom neměli používat žádné jiné hodnoty, protože mění pořadí prvků, což obvykle nechceme.
Pořadí tabulátoru se řídí pořadím prvků v HTML (DOM), nikoli vizuálním rozložením, které vytváříte pomocí CSS (např. flex-direction: column-reverse
nebo order
). Focus order při navigaci klávesnicí musí být logický a odpovídat vizuálnímu uspořádání stránky.
K tomuto tématu doporučuji přečíst článek Indicating focus to improve accessibility.
Cílem odkazů pro přeskočení je umožnit lidem přeskočit opakující se sekce nebo bloky obsahu na webových stránkách a usnadnit jim přístup k hlavnímu obsahu stránky. Nejčastějším příkladem je přeskočení hlavní navigace. Pro lidi, kteří používají navigaci pomocí klávesnice, je frustrující, když se na každé stránce musí proklikávat všemi odkazy.
Jak správně implementovat odkaz skip to content:
<!DOCTYPE html>
<html lang="cs">
<head>...</head>
<body>
<a href="#main-content" class="skip-link">Přejít na hlavní obsah</a>
<header>
<nav>
<a href="/">Domů</a>
<a href="/o-nas">O nás</a>
<a href="/kontakt">Kontakt</a>
</nav>
</header>
<main id="main-content">
<h1>Vítejte na naší stránce</h1>
<p>Toto je hlavní obsah, ku kterému se uživatel dostane po kliknutí na odkaz.</p>
<a href="/example">Další odkaz v obsahu</a>
</main>
</body>
</html>
.skip-link {
/* Vizuální skrytí odkazu mimo obrazovku */
position: absolute;
left: -999px;
top: -999px;
/* Stylujte podle designu stránky, např... */
padding: 20px;
backround-color: #FFF
color: primary;
font-size: 1.2em;
}
.skip-link:focus {
/* Zviditelnění odkazu, když dostane fokus (např. klávesou Tab) */
position: absolute; /* Nebo 'fixed', podle potřeby */
top: 0px;
left: 0px;
}
ℹ️ Inspirujte se například webem: smashingmagazine. Po načtení stránky stiskněte klávesu
Tab
. V levém horním rohu se zobrazí:Skip to main content
.
⚠️ Nepoužívejte v Next.js
<Link>
komponent, ale použijte klasický natívny HTML<a>
tag.Link
nesprávně implementuje změnu focus stavu. Více informací v GitHub issue.
Každý interaktivní prvek na stránce, např. tlačítka nebo odkazy, musí mít srozumitelný název. Často se stává, že např. odkazy obsahují pouze ikonu, ale žádný text. Čtečka obrazovky tak nevidomým uživatelům nedokáže přečíst, o jaký odkaz se jedná.
Pomocí tagu <span>
, který skryjeme CSS-kom, avšak pro čtečky obrazovky zůstane tento text stále viditelný, můžeme zobrazit popis pro tlačítka čistě se svg ikonami.
// CSS
.sr-only {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
<!-- HTML -->
<a href="https://twitter.com">
<svg aria-hidden="true" ...></svg>
<span class="sr-only">Twitter</span>
</a>
Na svg
element jsme použili atributy aria-hidden="true"
. Ikony na webu slouží primárně jako vizuální pomůcka, pro nevidomé však nemají žádný význam a zbytečně zahlcují důležitější obsah, např. text v tlačítku.
📚 Více informací o metodě skrytí vizuálního obsahu najdete na webu WebAIM.
// Špatné příklady ❌
<button>Facebook</button>
<button>LinkedIn</button>
// Dobré příklady ✅
<button>Zdílet článek na Facebooku</button>
<button>Zdílet článek na LinkedIn</button>
// Špatné příklady ❌
<a href="https://www.goodrequest.com/blog">Click here</a>
<a href="https://www.goodrequest.com/blog">Learn more</a>
<button>Edit</button>
Ideálním řešením je poskytnout smysluplný text pro tento odkaz/tlačítko. Pokud se tomu nemůžeme vyhnout, můžeme použít skrytý <span>
element:
<a href="/blog">
Show more
<span class="sr-only">blog articles</span>
</a>
<a href="/blog/{article.url}">
Read more
<span class="sr-only">about {article.name}</span>
</a>
<!-- Často tlačítka v tabulkách -->
<button onClick={handleChangeAddress(user.id)}>
Edit
<span class="sr-only">address for user {user.name}</span>
</a>
Pokud potřebujete kompletně přepsat obsah tlačítka pro čtečku obrazovky, můžete použít atribut aria-label
, ale dávejte pozor, aby se text v aria-label
shodoval s viditelným textem.
✅ SPRÁVNE:
<a href="url" aria-label="Read more about accessibility">Read more</a>
<button aria-label="Odeslat přihlášku">Odeslat</button>
❌ ŠPATNĚ (text se neshoduje): <button aria-label="Potvrdit formulář">Odeslat</button>
📚 Více o správném popisu odkazů naleznete ve WCAG specifikaci.
Formuláře na webu by měly být, stejně jako jiné interaktivní prvky, přístupné pomocí klávesnice. Například mezi vstupy se můžeme pohybovat pomocí klávesy Tab
, select by měl být možný pomocí šipek nahoru/dolů nebo checkbox označit mezerou.
Každý input musí mít přiřazen viditelný <label>
element. Můžeme použít dva zápisy pomocí for
atributu v kombinaci s id
nebo zabalením inputu do <label>
elementu.
<label for="name">Name:</label>
<input id="name" type="text" autocomplete="name">
<!-- or -->
<label>Name: <input type="text" autocomplete="name"></label>
Checkboxy nebo radio inputy můžeme seskupit pomocí <fieldset>
elementu a přiřadit jim popis pomocí <legend>
elementu. Pouhé vizuální seskupení inputů však není dostačující pro osoby, které používají čtečku obrazovky.
<fieldset>
<legend>Select your pizza toppings:</legend>
<input id="ham" type="checkbox" name="toppings" value="ham">
<label for="ham">Ham</label><br>
<input id="mushrooms" type="checkbox" name="toppings" value="mushrooms">
<label for="mushrooms">Mushrooms</label><br>
<input id="olives" type="checkbox" name="toppings" value="olives">
<label for="olives">Olives</label>
</fieldset>
required
nebo aria-required="true"
. Označit ho hvězdičkou nestačí, protože ne všechny čtečky obrazovky rozpoznají, že se jedná o povinný input.<input id="name" type="text" required>
<!-- or -->
<input id="name" type="text" aria-required="true">
aria-invalid="true"
, díky kterému čtečka obrazovky oznámí uživateli chybný stav.<input id="name" type="text" aria-invalid="true">
aria-describedby
. Hodnota aria-describedby
je id
elementu, který obsahuje chybovou zprávu. Čtečky obrazovky poté přečtou label pole, jeho typ, hodnotu a následně i chybovou zprávu.<label for="emailUzivatele">E-mail:</label>
<input aria-describedby="emailChyba" aria-invalid="true" id="emailUzivatele" type="email" name="email">
<div id="emailChyba" class="error-message">
Zadejte platnou e-mailovou adresu.
</div>
📚 Podrobně zpracované informace o přístupnosti formulářů najdete na WebAIM.
Na webu některé akce nezpůsobují úplné znovunačtení stránky. Uživatel může:
Vidící uživatel tyto změny vnímá vizuálně. Uživatel čtečky obrazovky se však nedozví, že se něco stalo.
<!-- automaticky přidává aria-live="polite" a aria-atomic="true" -->
<div role="status"> {{announcementString}} </div>
<!-- automaticky přidává aria-live="assertive" a aria-atomic="true" -->
<div role="alert"> {{announcementString}} </div>
Pro běžná (neurgentní) hlášení: role="status"
Čtečka zprávu přečte, když právě nesděluje jiný obsah (je iddle). Toto je volba pro 95% případů.
Pro urgentní hlášení: role="alert"
Pro kritické chyby nebo časově omezená varování, kde je nutná okamžitá pozornost uživatele, je varování oznámeno ihned a nečeká, až bude čtečka obrazovky idle.
aria-live
:"polite"
: Oznámí změnu když se nic jiného neděje."assertive"
: Okamžitě přeruší uživatele a oznámí změnu.aria-atomic
:"true"
: Přečte celý obsah elementu."false"
: Přečte pouze změněnou část.<div aria-live="polite" a aria-atomic="true"></div> <!-- totéž jako role="status" -->
<div aria-live="assertive" a aria-atomic="true"></div> <!-- totéž jako role="alert" -->
Další příklady jak použít aria-live
a aria-atomic
:
<!-- aria-atomic="false", protože chceme oznamovat jen nové zprávy -->
<div id="chat-log" aria-live="polite" aria-atomic="false">
<p>Jane Doe: Hi everyone!</p>
<p>John Smith: Hello there!</p>
</div>
<ul id="live-scores" aria-live="polite" aria-atomic="false">
<li>Team A vs. Team B: 1-0</li>
<li>Goal! Team A scores. The score is now 2-0.</li>
</ul>
<!-- aria-atomic="true",protože chceme oznamovat celý text a ne jen změny -->
<div id="search-summary" aria-live="polite" aria-atomic="true">
Showing 15 of 240 products.
</div>
<div id="stepper-status" aria-live="polite" aria-atomic="true">
Step 2 of 5: Shipping Details
</div>
<div id="toast-notification" aria-live="polite" aria-atomic="true">
File saved successfully.
</div>
<!-- aria-live="assertive", protože chceme oznámit kritickou chybu -->
<div aria-live="assertive" aria-atomic="true">
An unknown error occurred while saving your changes. Try again.
</div>
<div aria-live="assertive" aria-atomic="true">
Connection to server lost. Your changes may not be saved.
</div>
Příklad jak udělat přístupný search:
function AccessibleSearch() {
const [searchStatusMessage, setSearchStatusMessage] = useState('');
const handleSearch = (searchTerm: string) => {
// Search logic here
setSearchStatusMessage(`${resultsCount} results found for ${searchTerm}`)
};
return (
<>
{/* 1. Use a form with role="search" for a landmark */}
<form role="search" onSubmit={handleSearch}>
{/* 2. Connect the label to the input */}
<label htmlFor="search-input">Search our site</label>
<input
type="search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button type="submit">Search</button>
</form>
{/* 3. ARIA live region to announce status updates */}
<ScreenReaderOnlySC role="status">{searchStatusMessage}</ScreenReaderOnlySC>
</>
);
}
ℹ️ Mantine notifikace implementují ARIA Live Regions a korektně oznamují čtečkám obrazovky obsah notifikace.
Chyby musí být jasně označeny a popsány uživateli. Pokud uživatel udělá chybu při vyplňování webového formuláře, všechny chyby, které udělal, musí být jasně identifikovatelné. Chybová zpráva by měla přesně uvádět, co se pokazilo a jak to opravit.
Několik příkladů užitečných chybových hlášení:
Poskytnutí jasné zpětné vazby po úspěšném odeslání formuláře dává uživatelům potvrzení, že nemusí procházet formulář nebo stránku a hledat chyby. Zobrazením jasné a konzistentní zpětné vazby mohou uživatelé rychle pochopit, že jejich akce byla dokončena.
Každá akce, která vyžaduje složité gesto (potažení, přiblížení špetkou, tahání, třes, naklánění), musí být vykonatelná i jednoduchou akcí, například klepnutím na tlačítko nebo stisknutím klávesy. Žádná funkce se nesmí spoléhat pouze na pohybová gesta. Pohybové ovládání může být doplněk, ale nikdy ne jediný způsob, jak něco udělat.
Příklady:
Další
a Zpět
.+
a -
.Mapa vlevo spoléhá pouze na ovládání gesty (přiblížení špetkou a potažení). Mapa vpravo zobrazuje řádná tlačítka pro přiblížení a navigační šipky, které pomáhají uživatelům pochopit, jak obsah ovládat i bez použití polohovacích gest.