
GR8 Days 2025: Dva dni, ktoré nás znovu spojili

Každý web by mal byť navrhnutý tak, aby bol dostupný všetkým, od skúsených používateľov až po ľudí využívajúcich asistívne technológie. Aby sme to dokázali zabezpečiť konzistentne a bez kompromisov, potrebujeme jasne definované pravidlá.
Minimálny štandard prístupnosti slúži ako oficiálny podklad pre validáciu dizajnových návrhov, checklist pri code review a základ pre testovacie scenáre. Vďaka nemu dokážeme tvoriť weby, ktoré sú prístupné a komfortné pre všetkých používateľov.
📚 Veľmi dobre spracovaný materiál o všetkých pravidlách WCAG špecifikácie nájdete na webe: WCAG in Plain English.
HTML obsahuje množstvo tagov, ktoré sémanticky opisujú obsah na webe. Ak ho používame správne, napr. pre navigáciu použijeme <nav>
, každému inputu vyplníme <label>
alebo interaktívne prvky píšeme ako <button>
, tak čítačky obrazovky budú lepšie rozumieť obsahu a štruktúre webu.
Tip: Vygenerujte si sémantické HTML pomocou AI (odporúčam napr. Gemini PRO)
“Vygeneruj mi z tohto screenshotu prístupné HTML bez štýlov”
Gemini vygenerovalo:
<section aria-labelledby="benu-nadpis">
<div>
<img src="cesta-k-vasmu-obrazku.png" alt="Aplikácia Benu lekárne zobrazená na displeji mobilného telefónu.">
</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áva obsahu logickú štruktúru.alt
pri značke <img>
zaisťuje, že informácia z obrázka je dostupná aj pre nevidiacich.aria-labelledby
pomáha používateľom asistenčných technológií rýchlo sa zorientovať v obsahu stránky.aria-label
pri odkaze <a>
poskytuje jednoznačný popis cieľa odkazu, čo je kľúčové pre odkazy so všeobecným textom ako "Read more".<ul>
(netriedený zoznam) je dobrý, <dl>
(zoznam definícií) je ešte presnejší. Priamo komunikuje vzťah medzi hodnotou metriky (<dt>
) a jej popisom (<dd>
), čo asistenčné technológie dokážu interpretovať a lepšie prezentovať používateľovi.ℹ️ Okrem toho sémantické HTML pomáha vyhľadávačom lepšie pochopiť obsah stránky, čo má výrazný vplyv aj na pozíciu vo vyhľadávačoch.
Prečo je dôležité používať správne HTML tagy si môžeme ukázať na buttone:
▶️ CodeSandbox príklad ◀️
<!-- Zlý príklad ❌-->
<div className="button" onClick={handleClick}>
Open something
</div>
<!-- Dobrý príklad ✅ (onClick môže byť použitý iba na focusable elementoch) -->
<button className="button" onClick={handleClick}>
Open something
</button>
Na prvý pohľad button funguje. Keď kliknem myšou zavolá sa funkcia handleClick
rovnako ako pre div
, tak aj pre button
. Avšak ak sa na webe pohybujem pomocou klávesnice (kombinácia Tab
a Enter
), tak sa div
stáva nepoužiteľný. Natívnemu <button>
tagu prehliadač automaticky doplnil potrebnú funkcionalitu, napr. tabindex alebo focus stav.
📚 Veľmi dobre spracovaný materiál o všetkých HTML tagoch je prístupný na MDN Web Docs.
ARIA (Accessible Rich Internet Applications) je sada špeciálnych atribútov, ktoré môžete pridať do HTML kódu. Tieto atribúty používajú výhradne asistečné technológie a nijako neovplyvňujú vyrenderovaný obsah na webe, ale iba poskytujú dodatočné informácie pre asistenčné technológie.
⚠️ ARIA atribúty používajte s rozumom a ako poslednú možnosť ak neexistuje žiadna HTML alternatíva! Ak použijete ARIA atribúty nesprávne, môžete tým pokaziť prístupnosť webu ešte viac ako keby ich nepoužijete vôbec.
<div>
, toto je navigácia
alebo tlačidlo
.“role="navigation"
, role="dialog"
, role="button"
, role="search"
.<nav>
alebo <button>
, automaticky pridávajú rolu, a preto treba používať správne HTML tagy! Najlepšia ARIA je žiadna ARIA.role="tab"
, role="slider"
, samotná rola nepridá žiadnu funkčnosť. Iba informuje čítač obrazovky. Vašou úlohou zostáva doprogramovať ovládanie klávesnicou (napr. šípkami, Esc, Enter) pomocou JavaScriptu.❌ Zle: <div role="button" tabindex="0" onclick="mojaFunkcia()">Uložiť</div>
✅ Správne: <button onclick="mojaFunkcia()">Uložiť</button>
aria-labelledby="id-popisu"
(hovorí, kde nájsť textový popis), aria-required="true"
(označuje povinné políčko), aria-invalid="true"
(označuje, že hodnota je nesprávna).aria-expanded="false"
(označuje, že rozbaľovací prvok je zbalený), aria-hidden="true"
(skryje prvok pred čítačkou), aria-disabled="true"
(označuje, že prvok je neaktívny).📚 Viac o správnom používaní ARIA nájdete v dokumentácii MDN.
⚠️ Štandardné tagy HTML zvyčajne nepotrebujú implementovať ARIA atribúty, ale custom riešenia si vyžadujú zvýšenú starostlivosť. Zvyčajne na neštandardné funkcionality napr. dialog, swiper, datepicker… používame knižnice tretích strán. Uistite sa, že tieto knižnice implementujú správne ARIA atribúty!
Každá HTML stránka musí mať v sekcii <head>
unikátny a popisný element <title>
. Dobrý názov je jedinečný, stručný a výstižný. Držte sa nasledujúcich odporúčaní:
Špecifický názov stránky | Názov webu
.lang="sk"
). To umožňuje asistenčným technológiám, ako sú čítačky obrazovky, správne interpretovať a vyslovovať text.lang
pomocou JavaScriptulang
atribútu.<!DOCTYPE html>
<html lang="sk"> ✅ Vyplnený lang atribút v html tagu
<head>
<meta charset="UTF-8">
<title>Webdizajn na mieru | Názov Firmy</title> ✅ Vyplnený title
</head>
<body>
<h1>Naše služby v oblasti webdizajnu</h1>
<p>Ponúkame moderné a prístupné webové stránky...</p>
<blockquote lang="en"> ✅ Ak je text v inom jazyku ako 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>
Nie všetci používajú myš pre navigáciu na webe. Niektorí nemôžu použiť myš kvôli ochoreniu, skúsení používatelia ovládajú web pomocou klávesnice alebo na TV ovládame web cez diaľkový ovládač… Takýto používatelia sa spoliehajú na to, že všetky interaktívne elementy na webe, napr. buttony, odkazy alebo formulár, majú svoj focus stav.
Asi by sa vám nepáčilo keby sa na webe nezobrazoval kurzor. Avšak často toto robíme pre používateľov, ktorí sa orientujú na webe pomocou klávesnice.
body {
cursor: none; /* toto by ste určite na webe nespravili */
}
:focus {
outline: none; /* tak, prosím nerobte ani toto */
}
:focus
má však jednu vlastnosť, ktorá núti developerov outline
mazať. Pri kliknutí myšou, napr. na button, sa okolo neho zobrazuje rámik, čo nevyzerá dobre. V moderných prehliadačoch vieme tento problém jednoducho vyriešiť ak namiesto :focus
použijeme :focus-visible
. Tento selector už podporujú všetky moderné prehliadače a vďaka nemu sa už rámik zobrazuje len pri navigácii pomocou klávesnice (focusujeme na button cez Tab
klávesu).
Treba si dať pozor, aby focus stav bol dostatočne vizuálne odlíšený:
2px
hrubý a musí mať kontrast 3:1 (level AAA).Medzi focusnutým a nefocusnutým stavom musí byť rozdiel vo farebnom kontraste aspoň 3:1 (level AAA).
Focus je aspoň 2px hrubý a musí mať kontrast 3:1 voči nefocusnutému stavu (level AAA).
// Príklad ako si vieme pridať vlastný štýl pre focus stav
button:focus-visible {
outline: 3px solid deepskyblue;
outline-offset: 3px;
}
Interaktívne elementy, napr. button, formulárové polia alebo odkazy, sú tabovateľné a netreba dodatočne nič implementovať. Ak toto chceme docieliť napr. pre <div>
môžeme mu priradiť <div tabindex="0">
. Takúto možnosť vieme využiť napr. v interaktívnych grafoch, ktoré v HTML nemajú reprezentujúci tag. Ak chceme docieliť opačný efekt, teda vypnúť interaktivitu (nevieme element označiť cez Tab
klávesu) použijeme tabindex="-1"
. Žiadne iné hodnoty by sme pre tabindex
nemali používať, pretože menia poradie elementov, čo zvyčajne nechceme.
Poradie tabulátora sa riadi poradím prvkov v HTML (DOM), nie vizuálnym rozložením, ktoré vytvárate pomocou CSS (napr. flex-direction: column-reverse
alebo order
). Focus order pri navigácii klávesnicou musí byť logický a zodpovedať vizuálnemu usporiadaniu stránky.
K tejto téme odporúčam pozrieť článok Indicating focus to improve accessibility.
Cieľom odkazov na preskočenie je umožniť ľuďom preskočiť opakujúce sa sekcie alebo bloky obsahu na webových stránkach a uľahčiť im prístup k hlavnému obsahu stránky. Najčastejší príklad je preskočenie hlavnej navigácie. Pre ľudí, ktorí využívajú navigáciu pomocou klávesnice je frustrujúce, keď sa na každej stránke musia pretabovať všetkými odkazmi.
Ako správne implementovať skip to content odkaz:
<!DOCTYPE html>
<html lang="sk">
<head>...</head>
<body>
<a href="#main-content" class="skip-link">Preskočiť na hlavný obsah</a>
<header>
<nav>
<a href="/">Domov</a>
<a href="/o-nas">O nás</a>
<a href="/kontakt">Kontakt</a>
</nav>
</header>
<main id="main-content">
<h1>Vitajte na našej stránke</h1>
<p>Toto je hlavný obsah, ku ktorému sa používateľ dostane po kliknutí na odkaz.</p>
<a href="/example">Ďalší odkaz v obsahu</a>
</main>
</body>
</html>
.skip-link {
/* Vizuálne skrytie odkazu mimo obrazovky */
position: absolute;
left: -999px;
top: -999px;
/* Naštýlujte podľa dizajnu stránky napr... */
padding: 20px;
backround-color: #FFF
color: primary;
font-size: 1.2em;
}
.skip-link:focus {
/* Zviditeľnenie odkazu, keď dostane fokus (napr. klávesou Tab) */
position: absolute; /* Alebo 'fixed', podľa potreby */
top: 0px;
left: 0px;
}
ℹ️ Inšpirujte sa napr. webom: smashingmagazine. Po načítaní stránky stlačte
Tab
klávesu. V ľavom hornom rohu sa zobrazí:Skip to main content
.
⚠️ Nepoužívajte v Next.js
<Link>
komponent, ale použite klasický natívny HTML<a>
tag.Link
komponent nesprávne implementuje zmenu focus stavu. Viac info v GitHub issue.
Každý interaktívny prvok na stránke, napr. tlačidlá alebo odkazy, musí mať zrozumiteľný názov. Často sa stáva, že napr. odkazy v sebe majú len ikonku, ale žiadny text. Čítačka obrazovky tak nevie nevidiacemu používateľov prečítať, o aký odkaz ide.
Pomocou tagu <span>
, ktorý skryjeme CSS-kom, avšak pre čítačky obrazovky ostane tento text stále viditeľný, vieme zobraziť popis pre tlačidlá čisto so 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 sme použili atribúty aria-hidden="true"
. Ikonky na webe slúžia primárne ako vizuálna pomôcka, avšak pre nevidiacich nedávaju význam a zbytočne zahlcujú dôležitejši obsah, napr. text v tlačidle.
📚 Viac o métode pre skrývanie vizuálneho obsahu nájdete na webe WebAIM.
// Zlé príklady ❌
<button>Facebook</button>
<button>LinkedIn</button>
// Dobré príklady ✅
<button>Zdieľať článok na Facebooku</button>
<button>Zdieľať článok na LinkedIn</button>
// Zlé prí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álne riešenie je poskytnúť zmysluplný text pre tento odkaz/button. Ak sa tomu nevieme vyhnúť môžeme použiť 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čidlá v tabuľkách -->
<button onClick={handleChangeAddress(user.id)}>
Edit
<span class="sr-only">address for user {user.name}</span>
</a>
Ak potrebujete kompletne prepísať obsah tlačidla pre čítačku obrazovky, môžete použiť atribút aria-label
, avšak dajte si pozor, aby sa text v aria-label
zhodoval s viditeľným textom.
✅ DOBRE:
<a href="url" aria-label="Read more about accessibility">Read more</a>
<button aria-label="Odoslať prihlášku">Odoslať</button>
❌ ZLE (text sa nezhoduje): <button aria-label="Potvrdiť formulár">Odoslať</button>
📚 Viac o správnom popise odkazov nájdete vo WCAG špecifikácii.
Formuláre na webe by mali byť, rovnako ako iné interaktívne elementy, prístupné pomocou klávesnice. Napríklad presúvať sa medzi inputmi môžeme pomocou Tab
klávesy, select by sa mal dať vyberať pomocou šípok hore/dole alebo checkbox označiť medzerníkom.
Každý input musí mať priradený viditeľný <label>
element. Môžeme použiť dva zápisy pomocou for
atribútu v kombinácii s id
alebo 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 alebo radio inputy môžeme zgrupovať <fieldset>
elementom a priradiť popis pomocou <legend>
elementu. Len vizuálne zgrupovanie inputov nie je dostatočné pre ľudí, ktorí používajú čítač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
alebo aria-required="true"
. Označiť ho hviezdičkou nestačí, pretože nie všetky čítačky obrazovky rozoznajú, že ide o povinný input.<input id="name" type="text" required>
<!-- or -->
<input id="name" type="text" aria-required="true">
aria-invalid="true"
, vďaka ktorému čítačka obrazovky oznámi používateľovi chybný stav.<input id="name" type="text" aria-invalid="true">
aria-describedby
. Hodnota aria-describedby
je id
elementu, ktorý obsahuje chybovú správu. Čítačky obrazovky potom prečítajú label poľa, jeho typ, hodnotu a následne aj chybovú správu.<label for="emailPouzivatela">E-mail:</label>
<input aria-describedby="emailChyba" aria-invalid="true" id="emailPouzivatela" type="email" name="email">
<div id="emailChyba" class="error-message">
Zadajte platnú e-mailovú adresu.
</div>
📚 Dobre spracovaný materiál o prístupnosti formulárov nájdete na WebAIM.
Na webe niektoré akcie nespôsobujú úplné znovunačítanie stránky. Používateľ môže:
Vidiaci používateľ tieto zmeny vníma vizuálne. Používateľ čítačky obrazovky sa však nedozvie, že sa niečo stalo.
<!-- automaticky pridáva aria-live="polite" a aria-atomic="true" -->
<div role="status"> {{announcementString}} </div>
<!-- automaticky pridáva aria-live="assertive" a aria-atomic="true" -->
<div role="alert"> {{announcementString}} </div>
Pre bežné (neurgentné) hlásenia: role="status"
Čítačka správu prečíta, keď práve neoznamuje iný obsah (je iddle). Toto je voľba pre 95% prípadov.
Pre urgentné hlásenia: role="alert"
Pre kritické chyby alebo časovo obmedzené varovania, kde je potrebná okamžitá pozornosť používateľa, je varovanie oznámené ihneď a nečaká, kým bude čítačka obrazovky idle.
aria-live
:"polite"
: Oznámi zmenu keď sa nič iné nedeje."assertive"
: Okamžite preruší používateľa a oznámi zmenu.aria-atomic
:"true"
: Prečíta celý obsah elementu."false"
: Prečíta iba zmenenú časť.<div aria-live="polite" a aria-atomic="true"></div> <!-- to isté ako role="status" -->
<div aria-live="assertive" a aria-atomic="true"></div> <!-- to isté ako role="alert" -->
Ďalšie príklady ako použiť aria-live
a aria-atomic
:
<!-- aria-atomic="false", pretože chceme oznamovať len nové sprá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", pretože chceme oznamovať celý text a nie len zmeny -->
<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", pretože chceme oznámiť kritickú 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>
Príklad ako spraviť prí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 notifikácie implementujú ARIA Live Regions a korektne oznamujú čítačkám obrazovky obsah notifikácie.
Chyby musia byť jasne označené a opísané používateľovi. Ak používateľ urobí chybu pri vypĺňaní webového formulára, všetky chyby, ktoré urobil, musia byť jasne identifikovateľné. Chybové hlásenie by malo presne uvádzať, čo sa pokazilo a ako to opraviť.
Niekoľko príkladov užitočných chybových hlášok:
Poskytnutie jasnej spätnej väzby po úspešnom odoslaní formulára dáva používateľom potvrdenie, že nemusia prehľadávať formulár alebo stránku a hľadať chyby. Zobrazením jasnej a konzistentnej spätnej väzby môžu používatelia rýchlo pochopiť, že ich akcia bola dokončená.
Každá akcia, ktorá vyžaduje zložité gesto (potiahnutie, priblíženie štipkou, ťahanie, trasenie, nakláňanie), musí byť vykonateľná aj jednoduchou akciou, napríklad kliknutím na tlačidlo alebo stlačením klávesy. Žiadna funkcia sa nesmie spoliehať iba na pohybové gestá. Pohybové ovládanie môže byť doplnok, ale nikdy nie jediný spôsob, ako niečo urobiť.
Príklady:
Ďalej
a Späť
.+
a -
.Mapa vľavo sa spolieha iba na ovládanie gestami (priblíženie štipkou a potiahnutie). Mapa vpravo zobrazuje riadne tlačidlá na priblíženie a navigačné šípky, ktoré pomáhajú používateľom pochopiť, ako obsah ovládať aj bez použitia ukazovacích giest.