Potwierdzone sprawdzone punkt po punkcie · 2026-11-05
Pomiń nawigację
Informatyka · INF.03

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 nie jest zakazany

<div> używaj jako neutralnego opakowania do stylowania, gdy element nie ma znaczenia semantycznego. Nie zamieniaj nim jednak nagłówka, nawigacji ani stopki.

1. Który element zawiera główną treść strony?
1. Który element zawiera główną treść strony?
2. Po co stosuje się semantyczne elementy HTML?
2. Po co stosuje się semantyczne elementy HTML?
3. Który element grupuje linki nawigacyjne?
3. Który element grupuje linki nawigacyjne?

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.

Szkielet strony

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.

ElementRolaPrzykład użycia
headernagłówek strony lub sekcjilogo, tytuł, krótki opis
navgłówna nawigacjalista linków do podstron
maingłówna treść stronyartykuł, formularz, galeria
sectiontematyczna część treści„Moduły kursu”, „Kontakt”
articlesamodzielna treśćwpis, produkt, zadanie
asidetreść pobocznawskazówka, reklama, linki
footerstopka strony lub sekcjikontakt, 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">.

Najpierw HTML, potem ARIA

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 review

Oceń każdą linię.

4. Która kolejność nagłówków jest poprawna?
4. Która kolejność nagłówków jest poprawna?
5. Obraz jest czysto dekoracyjny. Jak zapisać jego atrybut alt?
5. Obraz jest czysto dekoracyjny. Jak zapisać jego atrybut alt?
6. Do czego służy atrybut aria-describedby?
6. Do czego służy atrybut aria-describedby?

Przykłady

Trzy przykłady pokazują kolejno: szkielet strony z regionów, nawigację z skip linkiem oraz dostępny formularz z komunikatem błędu.

Przykład 1 — szkielet strony

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.

HTML
<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>
CSS
.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;
}
Strona podzielona na regiony: ciemny header, jasny nav, main z sekcją artykułów i żółtą treścią poboczną oraz footer
Efekt kodu: regiony semantyczne tworzą czytelny szkielet strony, a nie stos divów.
  1. header zawiera tytuł i krótki opis strony — to nie to samo co nagłówek artykułu.
  2. nav z aria-label opisuje nawigację; lista linków jest w środku.
  3. main obejmuje treść unikalną dla strony, a section i article porządkują ją tematycznie.
  4. aside to treść poboczna, a footer zamyka 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.

Przykład 2 — skip link i focus

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.

HTML
<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>
CSS
.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;
}
Strona z widocznym skip linkiem Przejdź do treści, poziomą nawigacją i żółtą obwódką focusu
Efekt kodu: skip link jest widoczny po ustawieniu focusu, a aktywne linki mają wyraźną obwódkę.
  1. Skip link jest pierwszym linkiem na stronie i prowadzi do id="main".
  2. Poza focusem link jest schowany poza ekranem przez left: -9999px; po focusie wraca na widoczne miejsce.
  3. main ma tabindex="-1", żeby po kliknięciu linku focus przeniósł się do treści.
  4. :focus-visible rysuje 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.

Przykład 3 — dostępny formularz

Połącz pole z opisem i komunikatem błędu przez aria-describedby. Grupę pól zamknij w fieldset z legend.

HTML
<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>
CSS
.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;
}
Dostępny formularz z legendą Dane kontaktowe, czerwonym obramowaniem pola e-mail i komunikatem Adres e-mail jest niepoprawny
Efekt kodu: legenda opisuje grupę, pole jest połączone z pomocą i błędem, a błąd ma czerwony kolor i role="alert".
  1. fieldset i legend mówią, że pola dotyczą danych kontaktowych.
  2. aria-describedby="email-pomoc email-blad" wskazuje dwa elementy: podpowiedź i komunikat błędu.
  3. aria-invalid="true" informuje czytnik ekranu, że wartość pola jest niepoprawna.
  4. 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.
  5. novalidate wyłącza natywne dymki przeglądarki, bo w przykładzie pokazujemy własny komunikat i własne aria-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

zaznaczanie

Zaznacz trzy linie z błędem.

7. Skip link prowadzi do #main. Co musi mieć element main?
7. Skip link prowadzi do #main. Co musi mieć element main?
8. Dlaczego nie należy usuwać outline bez zamiennika?
8. Dlaczego nie należy usuwać outline bez zamiennika?
9. Jaki jest minimalny kontrast zwykłego tekstu według WCAG?
9. Jaki jest minimalny kontrast zwykłego tekstu według WCAG?

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 luk

Uzupeł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>
  1. Zadanie 1 — rozpoznaj regiony. W podanym fragmencie wskaż header, nav, main, section, article, aside i footer. Wyjaśnij, czym różni się section od article.

    <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

    header to nagłówek strony, nav — nawigacja, main — główna treść, section — część tematyczna, article — samodzielny wpis, aside — treść poboczna, a footer — stopka.

    section grupuje kilka elementów wokół tematu i zwykle ma nagłówek. article jest samodzielny — mógłby istnieć jako osobny wpis, produkt albo zadanie. W przykładzie „Wpisy” to sekcja, a „Pierwszy wpis” to artykuł.

  2. 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, section i footer. Tytuły zamieniamy na nagłówki h1 i h2, a listę linków zamykamy w ul.

    <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.

  3. 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 for i id, łączymy komunikat przez aria-describedby, a div zamieniamy na button 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-describedby wiąże pole z komunikatem, a button jest osiągalny z klawiatury i ma natywną rolę przycisku.

  4. Zadanie 4 — audyt strony. Strona ma: menu w div, obraz bez alt, dwa h1, 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ń na nav z listą linków;
    • obraz bez alt — dodaj opis, a jeśli obraz jest dekoracyjny, ustaw alt="";
    • dwa h1 — zostaw jeden główny, pozostałe zmień na h2 lub h3 zgodnie z hierarchią;
    • brak focusu — dodaj outline lub 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.

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.

10. Który zapis jest najlepszym przyciskiem dostępnym z klawiatury?
10. Który zapis jest najlepszym przyciskiem dostępnym z klawiatury?
11. Ile elementów main powinno być na jednej stronie?
11. Ile elementów main powinno być na jednej stronie?
12. Do czego służy fieldset z legend?
12. Do czego służy fieldset z legend?