HTML I CSS · INF.03 · MODUŁ 06
Semantyka i dostępność
Dobra strona jest zrozumiała nie tylko dla przeglądarki, ale też dla czytnika ekranu i klawiatury.
Wprowadzenie
HTML semantyczny to taki zapis, w którym elementy opisują rolę treści, a nie jej wygląd. <header> mówi „to nagłówek strony”, <nav> — „to nawigacja”, a <main> — „tu jest główna treść”. Zwykły <div> nie niesie takiej informacji.
Ma to znaczenie dla czytników ekranu, które pozwalają przeskakiwać między regionami strony. Dzięki nim osoba niewidząca nie musi czytać całego menu przy każdym wejściu. Semantyka pomaga też wyszukiwarkom i ułatwia utrzymanie kodu, bo od razu widać, która część strony jest czym.
Dostępność to nie tylko semantyka. Obraz potrzebuje alt, pole formularza — label, a każdy element interaktywny musi być osiągalny z klawiatury i mieć widoczny focus. Tekst powinien mieć odpowiedni kontrast, a dodatkowe opisy — aria-label albo aria-describedby.
Nagłówki tworzą spis treści strony. Na stronie jest jeden h1, potem h2 dla sekcji i h3 dla podsekcji. Nie przeskakuj z h1 od razu do h4 tylko dlatego, że mniejszy nagłówek wygląda lepiej — rozmiar zmieniasz w CSS.
<div> używaj jako neutralnego opakowania do stylowania, gdy element nie ma znaczenia semantycznego. Nie zamieniaj nim jednak nagłówka, nawigacji ani stopki.
Najważniejsza własność
Najważniejsza jest czytelna struktura: regiony strony i kolejne poziomy nagłówków. Dzięki niej zarówno człowiek, jak i czytnik ekranu wiedzą, gdzie jest menu, gdzie treść, a gdzie informacje poboczne. Regiony nie mogą być przypadkowe — main jest jeden, a nawigacja powinna być w nav.
header → nav → main → footer
h1 → h2 → h3 — bez przeskoków i bez kilku h1
Jeden main, jedno główne h1, każda sekcja z własnym nagłówkiem.
Elementy semantyczne mają swoje role. header to nagłówek strony lub sekcji: logo, tytuł, krótki opis. nav zawiera główne linki nawigacyjne. main obejmuje treść unikalną dla tej strony. section grupuje tematyczną część i zwykle ma nagłówek. article to samodzielna treść, np. wpis, produkt albo zadanie. aside zawiera treść poboczną: ciekawostkę, reklamę, powiązane linki. footer to stopka z danymi kontaktowymi i prawami autorskimi.
| Element | Rola | Przykład użycia |
|---|---|---|
header | nagłówek strony lub sekcji | logo, tytuł, krótki opis |
nav | główna nawigacja | lista linków do podstron |
main | główna treść strony | artykuł, formularz, galeria |
section | tematyczna część treści | „Moduły kursu”, „Kontakt” |
article | samodzielna treść | wpis, produkt, zadanie |
aside | treść poboczna | wskazówka, reklama, linki |
footer | stopka strony lub sekcji | kontakt, prawa autorskie |
Dostępność sprawdzasz w kilku miejscach. Obraz opisuj przez alt — jeśli jest dekoracyjny, zostaw alt="". Każde pole formularza połącz z label, a grupę pól zamknij w fieldset z legend. Na początku strony dodaj skip link do #main. Nie usuwaj obwódki focusu bez zamiennika. Kontrast tekstu wynosi co najmniej 4,5:1, a dużego tekstu i elementów interfejsu — 3:1. aria-label stosuj, gdy element nie ma widocznej etykiety, a aria-describedby — gdy chcesz powiązać pole z opisem lub komunikatem błędu.
Z klawiatury użytkownik porusza się klawiszem Tab, wraca Shift+Tab, a elementy aktywuje Enterem lub spacją. Wszystkie linki, przyciski i pola muszą być osiągalne i nie mogą uwięzić focusu. Jeśli musisz dodać rolę ARIA, najpierw sprawdź, czy nie ma natywnego elementu, który robi to samo — <button> jest lepszy niż <div role="button">.
Dobra semantyka i poprawny element często wystarczają. ARIA to uzupełnienie, a nie zamiennik natywnych znaczników. Źle użyte ARIA może bardziej zaszkodzić niż pomóc.
Most: od teorii do kodu
Oceń strukturę strony
Przeczytaj każdą linię i wybierz kategorię. Część linii jest poprawna.
Code review szkieletu strony
code reviewOceń każdą linię.
-
To nagłówek strony, więc powinien być element
<header>, a niedivz klasą. -
Nawigację opisuje
<nav>. Dzięki temu czytnik ekranu może do niej przeskoczyć. -
Poprawny region głównej treści. Na stronie powinien być tylko jeden
main. -
Jeden główny nagłówek strony jest poprawny.
-
Po
h1nie wolno przeskoczyć doh3. Najpierw powinien byćh2. -
Logo firmy niesie treść, więc potrzebuje opisu w
alt. Pustealtjest dla obrazów dekoracyjnych. -
Poprawny region stopki.
Przykłady
Trzy przykłady pokazują kolejno: szkielet strony z regionów, nawigację z skip linkiem oraz dostępny formularz z komunikatem błędu.
Zbuduj stronę z nagłówkiem, nawigacją, główną treścią, artykułami, treścią poboczną i stopką. Każdy region ma inne znaczenie i inną rolę. Selektory z .demo ograniczają style tylko do ramki zrzutu.
<header>
<h1>Kurs HTML i CSS</h1>
<p>Ucz się od podstaw.</p>
</header>
<nav aria-label="Nawigacja główna">
<ul>
<li><a href="#moduly">Moduły</a></li>
<li><a href="#kontakt">Kontakt</a></li>
</ul>
</nav>
<main id="main">
<section id="moduly">
<h2>Moduły kursu</h2>
<article>
<h3>Formularze</h3>
<p>Pola, etykiety i wysyłanie danych.</p>
</article>
<article>
<h3>Semantyka</h3>
<p>Regiony strony i dostępność.</p>
</article>
</section>
<aside aria-label="Wskazówka">
<h2>Wskazówka</h2>
<p>Zaczynaj od struktury, nie od kolorów.</p>
</aside>
</main>
<footer id="kontakt">
<p>Kontakt: kurs@example.com</p>
</footer>
.demo header,
.demo nav,
.demo footer {
padding: 14px 18px;
}
.demo header {
background: #1e293b;
color: #fff;
}
.demo header h1 {
margin: 0;
font-size: 24px;
}
.demo header p {
margin: 4px 0 0;
color: #cbd5e1;
}
.demo nav {
background: #e2e8f0;
border-bottom: 1px solid #cbd5e1;
}
.demo nav ul {
display: flex;
gap: 18px;
margin: 0;
padding: 0;
list-style: none;
}
.demo nav a {
color: #1d4ed8;
font-weight: 600;
}
.demo main {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 18px;
padding: 18px;
}
.demo section,
.demo aside {
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 14px;
}
.demo article {
margin-top: 12px;
padding: 10px 12px;
border-left: 4px solid #7c3aed;
background: #f8fafc;
}
.demo article h3 {
margin: 0 0 4px;
}
.demo aside {
background: #fffbeb;
}
.demo footer {
border-top: 1px solid #cbd5e1;
background: #f1f5f9;
}
headerzawiera tytuł i krótki opis strony — to nie to samo co nagłówek artykułu.navzaria-labelopisuje nawigację; lista linków jest w środku.mainobejmuje treść unikalną dla strony, asectioniarticleporządkują ją tematycznie.asideto treść poboczna, afooterzamyka stronę. Każdy region ma własne znaczenie.
Wynik: czytnik ekranu może przeskakiwać między header, nav, main i footer, a struktura nagłówków prowadzi od h1 do h3 bez przeskoków.
Dodaj link „Przejdź do treści”, który pojawia się po naciśnięciu Tab, oraz widoczną obwódkę focusu dla linków nawigacji. W demo skip link ma autofocus, żeby był widoczny na zrzucie; w zwykłej stronie użytkownik odkrywa go pierwszym Tabem.
<a class="skip" href="#main" autofocus>Przejdź do treści</a>
<header>
<h1>Dostępna nawigacja</h1>
</header>
<nav aria-label="Nawigacja główna">
<ul>
<li><a href="#main" aria-current="page">Start</a></li>
<li><a href="#kontakt">Kontakt</a></li>
</ul>
</nav>
<main id="main" tabindex="-1">
<h2>Treść strony</h2>
<p>Tu zaczyna się główna treść. Skip link prowadzi dokładnie tutaj.</p>
</main>
<footer id="kontakt">
<p>Kontakt: kurs@example.com</p>
</footer>
.skip {
position: absolute;
left: -9999px;
top: 12px;
padding: 10px 14px;
background: #1e293b;
color: #fff;
}
.skip:focus {
left: 12px;
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
.demo header {
padding: 14px 18px;
background: #1e293b;
color: #fff;
}
.demo header h1 {
margin: 0;
font-size: 24px;
}
.demo nav {
padding: 10px 18px;
background: #e2e8f0;
}
.demo nav ul {
display: flex;
gap: 18px;
margin: 0;
padding: 0;
list-style: none;
}
.demo nav a {
color: #1d4ed8;
font-weight: 600;
}
.demo a:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
.demo main {
padding: 18px;
}
.demo main:focus {
outline: 3px solid #f59e0b;
outline-offset: -3px;
}
.demo footer {
padding: 12px 18px;
border-top: 1px solid #cbd5e1;
background: #f1f5f9;
}
- Skip link jest pierwszym linkiem na stronie i prowadzi do
id="main". - Poza focusem link jest schowany poza ekranem przez
left: -9999px; po focusie wraca na widoczne miejsce. mainmatabindex="-1", żeby po kliknięciu linku focus przeniósł się do treści.:focus-visiblerysuje obwódkę tylko wtedy, gdy focus przyszedł z klawiatury. Nie usuwaj jej bez zamiennika.
Wynik: użytkownik klawiatury naciska Tab, widzi skip link, a po Enterze przechodzi od razu do treści zamiast klikać całe menu.
Połącz pole z opisem i komunikatem błędu przez aria-describedby. Grupę pól zamknij w fieldset z legend.
<form class="dostepny" action="/zapis" method="post" novalidate>
<h2>Zapisz się na warsztat</h2>
<fieldset>
<legend>Dane kontaktowe</legend>
<p class="pole">
<label for="email">Adres e-mail</label>
<input type="email" id="email" name="email"
aria-describedby="email-pomoc email-blad"
aria-invalid="true" required>
<span id="email-pomoc" class="pomoc">
Podaj adres w formacie nazwa@domena.pl.
</span>
<span id="email-blad" class="blad" role="alert">
Adres e-mail jest niepoprawny.
</span>
</p>
<p class="pole">
<label for="telefon">Telefon (9 cyfr)</label>
<input type="tel" id="telefon" name="telefon"
pattern="[0-9]{9}" aria-describedby="telefon-pomoc">
<span id="telefon-pomoc" class="pomoc">
Tylko cyfry, bez spacji.
</span>
</p>
</fieldset>
<button type="submit">Zapisz się</button>
</form>
.dostepny {
max-width: 520px;
margin: 0 auto;
}
.dostepny h2 {
margin: 0 0 16px;
}
.dostepny fieldset {
margin: 0 0 18px;
padding: 14px 16px;
border: 1px solid #cbd5e1;
border-radius: 10px;
}
.dostepny legend {
padding: 0 6px;
font-weight: 700;
}
.dostepny .pole {
margin: 0 0 16px;
}
.dostepny label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
.dostepny input {
width: 100%;
padding: 10px 12px;
border: 2px solid #94a3b8;
border-radius: 8px;
font: inherit;
}
.dostepny input[aria-invalid="true"] {
border-color: #dc2626;
}
.dostepny .pomoc {
display: block;
margin-top: 6px;
color: #475569;
font-size: 13px;
}
.dostepny .blad {
display: block;
margin-top: 4px;
color: #b91c1c;
font-size: 13px;
font-weight: 600;
}
.dostepny button {
padding: 10px 18px;
border: 0;
border-radius: 8px;
background: #2563eb;
color: #fff;
font: inherit;
cursor: pointer;
}
role="alert".fieldsetilegendmówią, że pola dotyczą danych kontaktowych.aria-describedby="email-pomoc email-blad"wskazuje dwa elementy: podpowiedź i komunikat błędu.aria-invalid="true"informuje czytnik ekranu, że wartość pola jest niepoprawna.role="alert"sprawia, że komunikat błędu jest odczytany od razu po pojawieniu się. W prawdziwym formularzu pokazuj go po sprawdzeniu, a nie od razu.novalidatewyłącza natywne dymki przeglądarki, bo w przykładzie pokazujemy własny komunikat i własnearia-describedby.
Wynik: użytkownik czytnika słyszy nazwę pola, podpowiedź i błąd, a osoba widząca widzi czerwone obramowanie oraz komunikat.
Most: od teorii do kodu
Znajdź błędy w dostępności
Zaznacz linie, które łamią zasady dostępności. Trzy linie są błędne.
Audyt prostego szkieletu
zaznaczanieZaznacz trzy linie z błędem.
-
Brakuje
alt. Czytnik ekranu nie wie, co przedstawia wykres. -
Brakuje
label.placeholderznika po wpisaniu tekstu i nie jest etykietą. -
divnie jest przyciskiem: nie da się go aktywować z klawiatury. Użyj<button>.
Spróbuj samodzielnie
Zanim napiszesz własną stronę, sprawdź, czy potrafisz czytać strukturę jak czytnik ekranu. Potem wykonaj zadania — od rozpoznawania regionów do pełnego audytu.
Most: od teorii do kodu
Uzupełnij regiony strony
Wybierz znaczniki, które pasują do opisu. Każdy region ma inne zadanie.
Brakujące znaczniki
uzupełnianie lukUzupełnij pięć luk.
<>
<h1>Moja strona</h1>
</>
< aria-label="Nawigacja główna">
<ul><li><a href="#main">Start</a></li></ul>
</nav>
< id="main">
<h2>Treść</h2>
</main>
<>
<p>Kontakt</p>
</footer>
Nagłówek strony to header, nawigacja — nav, główna treść — main, a stopka — footer. Każdy z tych elementów ma własną rolę.
-
Zadanie 1 — rozpoznaj regiony. W podanym fragmencie wskaż
header,nav,main,section,article,asideifooter. Wyjaśnij, czym różni sięsectionodarticle.<header><h1>Blog</h1></header> <nav><a href="/">Start</a></nav> <main> <section> <h2>Wpisy</h2> <article><h3>Pierwszy wpis</h3></article> </section> <aside><p>Reklama</p></aside> </main> <footer><p>Kontakt</p></footer>Pokaż rozwiązanie
headerto nagłówek strony,nav— nawigacja,main— główna treść,section— część tematyczna,article— samodzielny wpis,aside— treść poboczna, afooter— stopka.sectiongrupuje kilka elementów wokół tematu i zwykle ma nagłówek.articlejest samodzielny — mógłby istnieć jako osobny wpis, produkt albo zadanie. W przykładzie „Wpisy” to sekcja, a „Pierwszy wpis” to artykuł. -
Zadanie 2 — zamień divy na semantykę. Przepisz fragment tak, aby używać właściwych regionów. Zachowaj kolejność treści i dodaj brakujące nagłówki.
<div class="gora"> <div class="tytul">Moja firma</div> </div> <div class="menu"> <a href="#oferta">Oferta</a> <a href="#kontakt">Kontakt</a> </div> <div class="tresc"> <div class="sekcja"> <div class="naglowek">Oferta</div> <div class="opis">Tworzymy strony internetowe.</div> </div> </div> <div class="stopka">Kontakt: biuro@example.com</div>Pokaż rozwiązanie
Zamieniamy opakowania na elementy o znaczeniu:
header,nav,main,sectionifooter. Tytuły zamieniamy na nagłówkih1ih2, a listę linków zamykamy wul.<header> <h1>Moja firma</h1> </header> <nav aria-label="Nawigacja główna"> <ul> <li><a href="#oferta">Oferta</a></li> <li><a href="#kontakt">Kontakt</a></li> </ul> </nav> <main> <section id="oferta"> <h2>Oferta</h2> <p>Tworzymy strony internetowe.</p> </section> </main> <footer id="kontakt"> <p>Kontakt: biuro@example.com</p> </footer>Metoda: najpierw nazwij rolę każdego bloku, potem wybierz element. Klasy zostaw tam, gdzie nadal są potrzebne do stylowania, ale nie używaj ich zamiast znaczników semantycznych.
-
Zadanie 3 — napraw dostępność. W podanym formularzu popraw trzy błędy: brak etykiety, brak opisu błędu i przycisk zrobiony z
div.<form action="/zapis" method="post"> <input type="email" placeholder="E-mail" required> <p class="blad">Podaj poprawny e-mail.</p> <div class="przycisk">Wyślij</div> </form>Pokaż rozwiązanie
Dodajemy
label foriid, łączymy komunikat przezaria-describedby, adivzamieniamy nabutton type="submit".<form action="/zapis" method="post"> <label for="email">Adres e-mail</label> <input type="email" id="email" name="email" aria-describedby="email-blad" required> <p id="email-blad" class="blad">Podaj poprawny e-mail.</p> <button type="submit">Wyślij</button> </form>Etykieta daje nazwę pola,
aria-describedbywiąże pole z komunikatem, abuttonjest osiągalny z klawiatury i ma natywną rolę przycisku. -
Zadanie 4 — audyt strony. Strona ma: menu w
div, obraz bezalt, dwah1, przycisk bez widocznego focusu i szary tekst na białym tle o kontraście 2:1. Wskaż problemy i zaproponuj poprawki.Pokaż rozwiązanie
Problemy i poprawki:
- menu w
div— zamień nanavz listą linków; - obraz bez
alt— dodaj opis, a jeśli obraz jest dekoracyjny, ustawalt=""; - dwa
h1— zostaw jeden główny, pozostałe zmień nah2lubh3zgodnie z hierarchią; - brak focusu — dodaj
outlinelub inny wyraźny wskaźnik przy:focus-visible; - kontrast 2:1 — zwiększ kontrast tekstu do co najmniej 4,5:1.
Kolejność pracy: najpierw struktura i nagłówki, potem teksty alternatywne i etykiety, na końcu focus, kontrast i test z klawiaturą. Na końcu sprawdź stronę czytnikiem ekranu lub narzędziem audytowym.
- menu w
Podsumowanie. Używaj header, nav, main, section, article, aside i footer zgodnie z ich rolą. Pamiętaj o jednym h1 i kolejnych poziomach nagłówków bez przeskoków. Każdy obraz opisz przez alt, każde pole połącz z label, a grupy pól zamykaj w fieldset z legend. Dodaj skip link, nie usuwaj focusu i pilnuj kontrastu. ARIA stosuj dopiero wtedy, gdy HTML nie wystarcza.