Tech Handbook Null Yard

Nowoczesny HTML i CSS

HTML powinien najpierw opisywać znaczenie i strukturę treści, a CSS jej prezentację. Dobre podstawy semantycznego HTML, normal flow, Flexbox, Grid i responsywności pozwalają zbudować większość zwykłych stron bez dokładania frameworka.

Powiązane tematy: JavaScript - kompendium, UX i dostępność, Browser DevTools oraz Web Performance.

Praktyczne kompendium referencyjne dla osoby, która chce samodzielnie tworzyć współczesne, semantyczne, responsywne i dostępne strony internetowe.

HTML opisuje strukturę i znaczenie treści.
CSS odpowiada za wygląd, układ i prezentację.
JavaScript dodaje logikę i interakcję, gdy HTML i CSS nie wystarczają.


Spis treści

  1. HTML - podstawy
  2. Struktura dokumentu
  3. Semantyka HTML
  4. Tekst, listy i linki
  5. Obrazy i multimedia
  6. Tabele
  7. Formularze
  8. Dostępność
  9. CSS - podstawy
  10. Selektory
  11. Kaskada, dziedziczenie i specificity
  12. Box model
  13. Jednostki
  14. Kolory i zmienne CSS
  15. Typografia
  16. Normal flow
  17. Flexbox
  18. CSS Grid
  19. Pozycjonowanie
  20. Responsywność
  21. Container Queries
  22. Nowoczesny CSS
  23. Dark mode
  24. Animacje
  25. Formularze w CSS
  26. Reset / baza CSS
  27. Organizacja projektu
  28. Kompletny przykład strony
  29. Dobre praktyki
  30. Ściąga
  31. Co trzeba umieć

1. HTML - podstawy

HTML nie służy do rysowania strony. Jego zadaniem jest opisanie, czym są poszczególne elementy treści.

Przykład:

<article class="product">
    <h2>Example Laptop</h2>
    <p>Lekki notebook biznesowy.</p>
    <a href="/prestige-13">Zobacz produkt</a>
</article>

HTML mówi:

  • to jest samodzielny fragment treści,
  • ma nagłówek,
  • ma opis,
  • zawiera link.

CSS dopiero określa wygląd:

.product {
    padding: 2rem;
    border-radius: 1rem;
    background: white;
}

.product h2 {
    font-size: 1.5rem;
}

Nie warto używać nazw klas opisujących wyłącznie wygląd:

<div class="big-red-text">
    Example notebook
</div>

Jeśli coś jest nagłówkiem, lepiej napisać:

<h2 class="product-title">
    Example notebook
</h2>

2. Struktura dokumentu

Minimalny współczesny dokument HTML:

<!doctype html>
<html lang="pl">

<head>
    <meta charset="utf-8">

    <meta
        name="viewport"
        content="width=device-width, initial-scale=1"
    >

    <title>Moja strona</title>

    <meta
        name="description"
        content="Krótki opis strony."
    >

    <link rel="stylesheet" href="/css/style.css">
</head>

<body>

    <h1>Moja strona</h1>

</body>

</html>

<!doctype html>

Informuje przeglądarkę, że dokument używa współczesnego HTML.

Zawsze powinien być pierwszą linią pliku.

<html lang="pl">

Element główny dokumentu.

<html lang="pl">

Atrybut lang określa język dokumentu i pomaga:

  • czytnikom ekranu,
  • wyszukiwarkom,
  • tłumaczom automatycznym,
  • sprawdzaniu pisowni.

Zawiera metadane dokumentu.

Kodowanie:

<meta charset="utf-8">

Viewport:

<meta
    name="viewport"
    content="width=device-width, initial-scale=1"
>

Tytuł:

<title>Example Site - opowiadania</title>

Opis:

<meta
    name="description"
    content="Opowiadania grozy, weird fiction i science fiction."
>

CSS:

<link rel="stylesheet" href="/css/style.css">

Favicon:

<link rel="icon" href="/favicon.svg" type="image/svg+xml">

3. Semantyka HTML

Nowoczesna strona nie powinna być zbudowana wyłącznie z div.

Typowa struktura:

<body>

<header>
    ...
</header>

<nav>
    ...
</nav>

<main>
    ...
</main>

<footer>
    ...
</footer>

</body>

Nagłówek strony albo sekcji.

<header>
    <h1>Example Site</h1>
    <p>Opowiadania z miejsc, których nie ma.</p>
</header>

header może występować wiele razy.

<article>
    <header>
        <h2>Głosy z Blackwater</h2>
        <p>Jan Kowalski</p>
    </header>
</article>

<main>

Główna treść dokumentu:

<main>
    ...
</main>

Zwykle powinien istnieć jeden główny main.

Nawigacja:

<nav aria-label="Główna nawigacja">
    <a href="/">Start</a>
    <a href="/stories">Opowiadania</a>
    <a href="/cycles">Cykle</a>
    <a href="/about">O stronie</a>
</nav>

<section>

Tematyczna sekcja dokumentu:

<section>
    <h2>Najnowsze opowiadania</h2>
</section>

Nie używaj section jako automatycznego zamiennika div.

<article>

Samodzielna treść, która mogłaby istnieć niezależnie:

<article>
    <h2>Dom przy Gray Street</h2>
    <p>...</p>
</article>

Typowe zastosowania:

  • wpis blogowy,
  • artykuł,
  • karta produktu,
  • news,
  • komentarz,
  • opowiadanie.

<aside>

Treść poboczna:

<aside>
    <h2>Podobne historie</h2>
</aside>

Stopka dokumentu lub sekcji:

<footer>
    <p>&copy; 2026 Example Site</p>
</footer>

<div>

Neutralny kontener:

<div class="wrapper">
    ...
</div>

div jest prawidłowy, kiedy potrzebujemy elementu wyłącznie do layoutu albo stylowania.


4. Tekst, listy i linki

Nagłówki

HTML ma sześć poziomów nagłówków:

<h1>...</h1>
<h2>...</h2>
<h3>...</h3>
<h4>...</h4>
<h5>...</h5>
<h6>...</h6>

Przykład:

<h1>Kompendium FreeBSD</h1>

<h2>System plików</h2>

<h3>ZFS</h3>

<h4>Snapshoty</h4>

Nagłówki określają hierarchię treści, nie jej wygląd.

Akapity

<p>
    To jest akapit tekstu.
</p>

Wyróżnienia

<strong>bardzo ważne</strong>

strong oznacza znaczeniową wagę.

<em>naprawdę</em>

em oznacza akcent.

Cytaty

Krótki cytat:

<p>
    Autor napisał <q>to jest cytat</q>.
</p>

Dłuższy cytat:

<blockquote>
    <p>Tekst cytatu.</p>
</blockquote>

Kod

Kod w tekście:

<code>sudo apt update</code>

Blok kodu:

<pre><code>sudo apt update
sudo apt upgrade</code></pre>

Listy

Nieuporządkowana:

<ul>
    <li>Debian</li>
    <li>FreeBSD</li>
    <li>OpenBSD</li>
</ul>

Numerowana:

<ol>
    <li>Zainstaluj system</li>
    <li>Zaktualizuj pakiety</li>
    <li>Skonfiguruj nginx</li>
</ol>

Lista definicji:

<dl>
    <dt>HTML</dt>
    <dd>Struktura dokumentu.</dd>

    <dt>CSS</dt>
    <dd>Wygląd dokumentu.</dd>
</dl>

Linki

<a href="/about">O nas</a>

Link zewnętrzny:

<a href="https://example.com">
    Example
</a>

Nowa karta:

<a
    href="https://example.com"
    target="_blank"
    rel="noopener"
>
    Example
</a>

E-mail:

<a href="mailto:test@example.com">
    Napisz do nas
</a>

Telefon:

<a href="tel:+48123456789">
    123 456 789
</a>

Kotwica:

<a href="#kontakt">Kontakt</a>

<section id="kontakt">
    <h2>Kontakt</h2>
</section>

Link służy do nawigacji:

<a href="/products">
    Produkty
</a>

Przycisk służy do wykonania akcji:

<button type="button">
    Otwórz menu
</button>

Nie warto robić:

<div onclick="...">Kliknij</div>

jeśli element faktycznie jest przyciskiem.


5. Obrazy i multimedia

Obraz

<img
    src="/images/castle.jpg"
    alt="Ruiny zamku na wzgórzu"
>

alt opisuje znaczenie grafiki.

Jeżeli obraz jest dekoracyjny:

<img
    src="/images/decoration.svg"
    alt=""
>

Rozmiary

Dobrze podawać rozmiary:

<img
    src="photo.jpg"
    alt="Stary dom"
    width="1200"
    height="800"
>

CSS:

img {
    max-width: 100%;
    height: auto;
}

srcset

<img
    src="photo-800.jpg"
    srcset="
        photo-480.jpg 480w,
        photo-800.jpg 800w,
        photo-1600.jpg 1600w
    "
    sizes="
        (max-width: 700px) 100vw,
        800px
    "
    alt="Widok miasta"
>

Przeglądarka sama wybiera odpowiedni plik.

<picture>

<picture>

    <source
        srcset="hero.avif"
        type="image/avif"
    >

    <source
        srcset="hero.webp"
        type="image/webp"
    >

    <img
        src="hero.jpg"
        alt="Górski krajobraz"
    >

</picture>

Lazy loading

<img
    src="photo.jpg"
    alt="..."
    loading="lazy"
>

Nie dodawaj loading="lazy" do najważniejszego obrazu od razu widocznego na ekranie.

figure

<figure>

    <img
        src="server.jpg"
        alt="Serwer stojący w szafie rack"
    >

    <figcaption>
        Domowy serwer FreeBSD.
    </figcaption>

</figure>

6. Tabele

Tabele służą do danych tabelarycznych.

<table>

    <caption>Ceny serwerów</caption>

    <thead>
        <tr>
            <th scope="col">Serwer</th>
            <th scope="col">RAM</th>
            <th scope="col">Cena</th>
        </tr>
    </thead>

    <tbody>
        <tr>
            <th scope="row">VPS-1</th>
            <td>4 GB</td>
            <td>20 zł</td>
        </tr>

        <tr>
            <th scope="row">VPS-2</th>
            <td>8 GB</td>
            <td>40 zł</td>
        </tr>
    </tbody>

</table>

Nie używaj tabel do budowania layoutu strony.


7. Formularze

Podstawowy formularz:

<form action="/contact" method="post">

    <label for="name">
        Imię
    </label>

    <input
        id="name"
        name="name"
        type="text"
    >

    <button type="submit">
        Wyślij
    </button>

</form>

name określa nazwę pola przesyłaną do backendu.

id pozwala między innymi powiązać pole z label.

Typy inputów

<input type="text">
<input type="email">
<input type="password">
<input type="number">
<input type="date">
<input type="checkbox">
<input type="radio">
<input type="file">
<input type="url">
<input type="search">

Walidacja

Pole wymagane:

<input
    type="email"
    name="email"
    required
>

Minimalna długość:

<input
    type="text"
    minlength="3"
>

Maksymalna długość:

<input
    type="text"
    maxlength="100"
>

Zakres:

<input
    type="number"
    min="1"
    max="100"
>

Wzorzec:

<input
    type="text"
    pattern="[0-9]{6}"
>

Walidacja HTML nie zastępuje walidacji backendowej.

textarea

<label for="message">
    Wiadomość
</label>

<textarea
    id="message"
    name="message"
    rows="8"
></textarea>

select

<label for="country">
    Kraj
</label>

<select id="country" name="country">

    <option value="">Wybierz</option>
    <option value="pl">Polska</option>
    <option value="de">Niemcy</option>
    <option value="cz">Czechy</option>

</select>

fieldset

<fieldset>

    <legend>Rodzaj konta</legend>

    <label>
        <input
            type="radio"
            name="account"
            value="private"
        >
        Prywatne
    </label>

    <label>
        <input
            type="radio"
            name="account"
            value="business"
        >
        Firmowe
    </label>

</fieldset>

autocomplete

<input
    type="text"
    name="name"
    autocomplete="name"
>
<input
    type="email"
    autocomplete="email"
>
<input
    type="password"
    autocomplete="current-password"
>

details

Prosty element rozwijany bez JavaScriptu:

<details>

    <summary>
        Więcej informacji
    </summary>

    <p>
        Tutaj znajduje się dodatkowa treść.
    </p>

</details>

data-*

<article
    data-product-id="123"
    data-category="laptop"
>

JavaScript może później odczytać te dane.


8. Dostępność

Dostępność zaczyna się od poprawnego HTML.

Lepiej:

<button>
    Otwórz
</button>

niż:

<div role="button">
    Otwórz
</div>

Lepiej:

<label for="email">
    E-mail
</label>

<input
    id="email"
    type="email"
>

niż pole bez etykiety.

ARIA

ARIA jest przydatna wtedy, gdy natywny HTML nie wystarcza.

<nav aria-label="Menu główne">
<button
    aria-expanded="false"
    aria-controls="main-menu"
>
    Menu
</button>

Ogólna zasada:

Jeżeli istnieje odpowiedni natywny element HTML, użyj go zamiast odtwarzać jego zachowanie ARIA.


9. CSS - podstawy

Podłączanie:

<link rel="stylesheet" href="/css/style.css">

Podstawowa składnia:

selector {
    property: value;
}

Przykład:

h1 {
    color: navy;
    font-size: 3rem;
}

10. Selektory

Element:

p {
}

Klasa:

.card {
}

ID:

#header {
}

Atrybut:

input[type="email"] {
}

Kilka selektorów:

h1,
h2,
h3 {
    font-family: sans-serif;
}

Potomek:

article p {
}

Bezpośrednie dziecko:

article > p {
}

Następny element:

h2 + p {
}

Późniejsze rodzeństwo:

h2 ~ p {
}

Pseudoklasy

a:hover {
    color: red;
}
button:focus-visible {
    outline: 3px solid blue;
}
li:first-child {
}
li:last-child {
}
li:nth-child(even) {
}

:not()

button:not(.primary) {
    opacity: 0.8;
}

:is()

article :is(h1, h2, h3) {
    color: navy;
}

:where()

:where(article, section, aside) p {
    line-height: 1.6;
}

where() nie zwiększa specificity.

:has()

.card:has(img) {
    padding-top: 0;
}
.field:has(input:invalid) {
    border-color: red;
}
body:has(dialog[open]) {
    overflow: hidden;
}

Pseudoelementy

.external-link::after {
    content: " ↗";
}

Najczęstsze:

::before
::after

11. Kaskada, dziedziczenie i specificity

CSS oznacza Cascading Style Sheets.

Jeżeli kilka reguł dotyczy tego samego elementu, przeglądarka ustala, która deklaracja wygrywa.

Przykład:

p {
    color: red;
}

.text {
    color: blue;
}

Dla:

<p class="text">

wygrywa .text.

Na wynik wpływają między innymi:

  • źródło stylu,
  • !important,
  • @layer,
  • specificity,
  • kolejność reguł.

W uproszczeniu:

p

jest słabsze niż:

.text

a to jest słabsze niż:

#article

Nie warto budować selektorów typu:

body main section article div.card p.description {
}

Lepiej:

.card-description {
}

!important

color: red !important;

Używaj wyjątkowo.

Jeśli jest potrzebne wszędzie, architektura CSS prawdopodobnie wymaga poprawy.

Dziedziczenie

Właściwości typograficzne często są dziedziczone:

body {
    color: #222;
    font-family: sans-serif;
}

Natomiast między innymi:

margin
padding
border

nie są normalnie dziedziczone.


12. Box model

Każdy element jest pudełkiem:

margin
└── border
    └── padding
        └── content

Przykład:

.card {
    width: 300px;
    padding: 20px;
    border: 2px solid black;
    margin: 20px;
}

box-sizing

Bardzo przydatna baza:

*,
*::before,
*::after {
    box-sizing: border-box;
}

Wtedy width obejmuje również padding i border.

Margin

margin: 2rem;
margin: 1rem 2rem;
margin: 1rem 2rem 3rem 4rem;

Kolejność:

góra prawa dół lewa

Padding

padding: 1rem;

Właściwości logiczne

Zamiast:

margin-left: 2rem;
margin-right: 2rem;

można użyć:

margin-inline: 2rem;

Zamiast:

margin-top: 1rem;
margin-bottom: 1rem;

można:

margin-block: 1rem;

Podobnie:

padding-inline
padding-block
border-inline
border-block
inline-size
block-size

13. Jednostki

px

Dobre między innymi dla cienkich granic:

border: 1px solid;

rem

padding: 2rem;

Dobre do:

  • odstępów,
  • fontów,
  • komponentów.

em

Zależne od aktualnego rozmiaru fontu:

button {
    padding: 0.7em 1.2em;
}

%

width: 50%;

vw i vh

width: 100vw;
min-height: 100vh;

Nowoczesne warianty:

svh
lvh
dvh

Na urządzeniach mobilnych często warto:

.hero {
    min-height: 100dvh;
}

min()

width: min(100%, 1200px);

max()

width: max(50%, 400px);

clamp()

Płynna typografia:

h1 {
    font-size: clamp(
        2rem,
        5vw,
        5rem
    );
}

Czyli:

  • minimum: 2rem,
  • wartość preferowana: 5vw,
  • maksimum: 5rem.

calc()

width: calc(100% - 2rem);
height: calc(100vh - 80px);

14. Kolory i zmienne CSS

Hex:

color: #ff0000;

RGB:

color: rgb(255 0 0);

Alpha:

color: rgb(255 0 0 / 50%);

HSL:

color: hsl(0 100% 50%);

currentColor

.button {
    color: blue;
    border: 1px solid currentColor;
}

color-mix()

background:
    color-mix(
        in srgb,
        var(--primary) 20%,
        white
    );

Custom Properties

:root {
    --color-bg: #fff;
    --color-text: #111;
    --color-primary: #4058d6;

    --space-small: 0.5rem;
    --space-medium: 1rem;
    --space-large: 2rem;
}

Użycie:

body {
    background: var(--color-bg);
    color: var(--color-text);
}

Fallback:

color: var(--link-color, blue);

Zmienne można nadpisywać lokalnie:

.card {
    --card-color: blue;
}

.card.warning {
    --card-color: red;
}

15. Typografia

Typowa baza:

body {
    font-family:
        system-ui,
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        sans-serif;

    font-size: 1rem;
    line-height: 1.6;

    color: #222;
}

Długość tekstu

Bardzo szerokie akapity źle się czyta:

.article {
    max-width: 70ch;
}

ch świetnie sprawdza się przy szerokości treści tekstowej.


16. Normal flow

HTML ma domyślny przepływ dokumentu.

Elementy blokowe:

<div>
<p>
<section>
<article>

układają się jeden pod drugim.

Zanim użyjesz Grid, Flexbox albo position, sprawdź, czy zwykły flow nie wystarcza.


17. Flexbox

Flexbox służy głównie do układania elementów w jednym wymiarze.

.navigation {
    display: flex;
    gap: 1rem;
}

Kierunek

Domyślnie:

flex-direction: row;

Pionowo:

flex-direction: column;

justify-content

Steruje osią główną:

justify-content: center;
justify-content: space-between;
justify-content: space-around;
justify-content: flex-start;
justify-content: flex-end;

align-items

Oś poprzeczna:

align-items: center;

Typowy wzorzec:

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

gap

display: flex;
gap: 1.5rem;

Zawijanie

display: flex;
flex-wrap: wrap;
gap: 1rem;

flex

.content {
    flex: 1;
}

Przykład layoutu:

.layout {
    display: flex;
    gap: 2rem;
}

.sidebar {
    flex: 0 0 250px;
}

.content {
    flex: 1;
}

18. CSS Grid

Grid służy do układów dwuwymiarowych.

.grid {
    display: grid;

    grid-template-columns:
        1fr 1fr 1fr;

    gap: 2rem;
}

fr

grid-template-columns:
    1fr 2fr;

Druga kolumna dostaje dwa razy więcej dostępnego miejsca.

repeat()

grid-template-columns:
    repeat(3, 1fr);

minmax()

grid-template-columns:
    repeat(
        3,
        minmax(200px, 1fr)
    );

Responsywny Grid

Bardzo ważny wzorzec:

.cards {
    display: grid;

    grid-template-columns:
        repeat(
            auto-fit,
            minmax(250px, 1fr)
        );

    gap: 2rem;
}

Bez media query otrzymujemy:

  • więcej kolumn na szerokim ekranie,
  • mniej kolumn na węższym,
  • jedną kolumnę na telefonie.

Grid Areas

.layout {
    display: grid;

    grid-template-columns:
        250px 1fr;

    grid-template-areas:
        "sidebar content";
}
.sidebar {
    grid-area: sidebar;
}

.content {
    grid-area: content;
}

subgrid

.cards {
    display: grid;
    grid-template-columns:
        repeat(3, 1fr);
}

.card {
    display: grid;

    grid-template-rows:
        subgrid;

    grid-row:
        span 3;
}

Flexbox czy Grid?

Flexbox:

Ułóż elementy w rzędzie albo kolumnie.

Grid:

Potrzebuję kontroli nad kolumnami i wierszami.


19. Pozycjonowanie

Najważniejsze wartości:

position: static;
position: relative;
position: absolute;
position: fixed;
position: sticky;

relative

.card {
    position: relative;
}

Często służy jako punkt odniesienia dla elementów absolutnych.

absolute

.badge {
    position: absolute;
    top: 1rem;
    right: 1rem;
}

Typowy wzorzec:

.card {
    position: relative;
}

.badge {
    position: absolute;
    inset-block-start: 1rem;
    inset-inline-end: 1rem;
}

fixed

.cookie-button {
    position: fixed;
    right: 2rem;
    bottom: 2rem;
}

sticky

.sidebar {
    position: sticky;
    top: 1rem;
}

z-index

.modal {
    z-index: 100;
}

Nie rozwiązuj problemów przez:

z-index: 999999999;

Jeżeli z-index nie działa zgodnie z oczekiwaniem, sprawdź stacking context.

Overflow

overflow: hidden;
overflow: auto;
overflow: scroll;

Kod:

.code {
    overflow-x: auto;
}

aspect-ratio

.thumbnail {
    aspect-ratio: 16 / 9;
}

object-fit

.thumbnail img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

Kontener strony

.container {
    width:
        min(
            100% - 2rem,
            1200px
        );

    margin-inline: auto;
}

20. Responsywność

Najpraktyczniejsze podejście to mobile first.

Bazowy layout:

.cards {
    display: grid;
    gap: 1rem;
}

Dla większych ekranów:

@media (width >= 700px) {
    .cards {
        grid-template-columns:
            repeat(2, 1fr);
    }
}

Jeszcze szerzej:

@media (width >= 1100px) {
    .cards {
        grid-template-columns:
            repeat(3, 1fr);
    }
}

Nowoczesna składnia

Zamiast:

@media (min-width: 768px) {
}

można:

@media (width >= 768px) {
}

Zakres:

@media (600px <= width < 1000px) {
}

Breakpointy

Breakpoint powinien wynikać z layoutu, a nie z listy konkretnych modeli telefonów.

Nie myśl:

iPhone = X
tablet = Y
desktop = Z

Myśl:

W którym miejscu layout przestaje wyglądać dobrze?


21. Container Queries

Media query reaguje na viewport.

Container query reaguje na szerokość kontenera.

.card-wrapper {
    container-type: inline-size;
}
@container (width >= 500px) {

    .card {
        display: grid;
        grid-template-columns:
            200px 1fr;
    }

}

Dzięki temu ten sam komponent może wyglądać inaczej w sidebarze i inaczej w głównej kolumnie.

Nazwane kontenery

.products {
    container:
        products / inline-size;
}
@container products (width >= 700px) {
    ...
}

22. Nowoczesny CSS

@layer

Pozwala jawnie kontrolować kolejność warstw:

@layer reset, base, components, utilities;
@layer reset {

    * {
        box-sizing: border-box;
    }

}
@layer base {

    body {
        font-family: system-ui;
    }

}
@layer components {

    .button {
        padding: 0.7rem 1rem;
    }

}
@layer utilities {

    .hidden {
        display: none;
    }

}

CSS Nesting

Zamiast:

.card {
    padding: 2rem;
}

.card h2 {
    font-size: 2rem;
}

.card a {
    color: blue;
}

.card a:hover {
    color: red;
}

można:

.card {
    padding: 2rem;

    h2 {
        font-size: 2rem;
    }

    a {
        color: blue;

        &:hover {
            color: red;
        }
    }
}

Media query wewnątrz:

.card {
    padding: 1rem;

    @media (width >= 700px) {
        padding: 2rem;
    }
}

Prosta konwencja klas

.card
.card-title
.card-image
.card-description
.card-actions

Stan:

.card.is-active

Wariant:

.card.card-featured

Design tokens

:root {

    --color-bg: #fafafa;
    --color-surface: #fff;
    --color-text: #171717;
    --color-muted: #666;
    --color-primary: #4058d6;
    --color-border: #ddd;

    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 1rem;
    --space-4: 1.5rem;
    --space-5: 2rem;
    --space-6: 3rem;

    --radius-small: 0.3rem;
    --radius-medium: 0.7rem;
    --radius-large: 1.2rem;
}

23. Dark mode

:root {
    color-scheme: light dark;

    --bg: white;
    --text: #181818;
}
@media (prefers-color-scheme: dark) {

    :root {
        --bg: #111;
        --text: #eee;
    }

}
body {
    background: var(--bg);
    color: var(--text);
}

24. Animacje

Transition

.button {
    background: blue;

    transition:
        background 200ms ease,
        transform 200ms ease;
}

.button:hover {
    background: navy;
    transform: translateY(-2px);
}

Lepiej określać konkretne właściwości niż pisać:

transition: all 1s;

Transform

transform: translateX(10px);
transform: translateY(-5px);
transform: scale(1.05);
transform: rotate(5deg);

Łączenie:

transform:
    translateY(-2px)
    scale(1.02);

@keyframes

@keyframes pulse {

    0% {
        transform: scale(1);
    }

    50% {
        transform: scale(1.05);
    }

    100% {
        transform: scale(1);
    }

}
.logo {
    animation:
        pulse 2s infinite;
}

Reduced motion

@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms;
        animation-iteration-count: 1;
        transition-duration: 0.01ms;
    }

}

25. Formularze w CSS

.form {
    display: grid;
    gap: 1.5rem;
}

.field {
    display: grid;
    gap: 0.4rem;
}

input,
textarea,
select {
    width: 100%;

    padding: 0.8rem 1rem;

    font: inherit;

    border: 1px solid #aaa;
    border-radius: 0.4rem;

    background: white;
    color: #111;
}

Focus

Nie usuwaj focusa bez zastąpienia go czymś sensownym.

input:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible {

    outline:
        3px solid
        royalblue;

    outline-offset: 3px;
}

Stan niepoprawny

input:invalid:not(:placeholder-shown) {
    border-color: red;
}

26. Reset / baza CSS

Rozsądny punkt startowy:

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    hanging-punctuation:
        first last;
}

body {
    margin: 0;

    min-height: 100dvh;

    font-family:
        system-ui,
        sans-serif;

    line-height: 1.6;
}

img,
picture,
svg,
video {
    display: block;
    max-width: 100%;
}

input,
button,
textarea,
select {
    font: inherit;
}

h1,
h2,
h3,
h4 {
    line-height: 1.2;
}

p,
h1,
h2,
h3,
h4 {
    overflow-wrap:
        break-word;
}

27. Organizacja projektu

Mały projekt:

project/
│
├── index.html
├── about.html
├── contact.html
│
├── css/
│   └── style.css
│
├── images/
│   ├── hero.webp
│   └── logo.svg
│
└── js/
    └── main.js

Większy projekt:

css/
│
├── reset.css
├── tokens.css
├── base.css
├── layout.css
├── components.css
└── utilities.css

Nie dziel CSS na dziesiątki plików bez realnej potrzeby.


28. Kompletny przykład strony

HTML

<!doctype html>

<html lang="pl">

<head>

    <meta charset="utf-8">

    <meta
        name="viewport"
        content="width=device-width, initial-scale=1"
    >

    <title>Null Yard</title>

    <meta
        name="description"
        content="Eksperymentalne projekty programistyczne."
    >

    <link
        rel="stylesheet"
        href="style.css"
    >

</head>

<body>

<header class="site-header">

    <div class="container header-inner">

        <a
            class="logo"
            href="/"
        >
            Null Yard
        </a>

        <nav
            class="navigation"
            aria-label="Główna nawigacja"
        >
            <a href="#projects">Projekty</a>
            <a href="#about">O mnie</a>
            <a href="#contact">Kontakt</a>
        </nav>

    </div>

</header>


<main>

<section class="hero">

    <div class="container">

        <p class="hero-label">
            Software experiments
        </p>

        <h1>
            Buduję rzeczy,
            które rozwiązują
            prawdziwe problemy.
        </h1>

        <p class="hero-description">
            Go, HTML, CSS, automatyzacja
            i eksperymenty z AI.
        </p>

        <a
            class="button"
            href="#projects"
        >
            Zobacz projekty
        </a>

    </div>

</section>


<section
    class="projects"
    id="projects"
>

    <div class="container">

        <header class="section-header">

            <h2>Projekty</h2>

            <p>
                Kilka rzeczy,
                nad którymi pracuję.
            </p>

        </header>

        <div class="project-grid">

            <article class="card">

                <h3>Web Monitor</h3>

                <p>
                    Automatyczne monitorowanie
                    stron promocji.
                </p>

                <a href="#">
                    Zobacz projekt
                </a>

            </article>

            <article class="card">

                <h3>Example Site</h3>

                <p>
                    Minimalistyczna platforma
                    do publikacji opowiadań.
                </p>

                <a href="#">
                    Zobacz projekt
                </a>

            </article>

            <article class="card">

                <h3>Hermes Tools</h3>

                <p>
                    Eksperymenty z agentami AI
                    i automatyzacją.
                </p>

                <a href="#">
                    Zobacz projekt
                </a>

            </article>

        </div>

    </div>

</section>


<section
    class="about"
    id="about"
>

    <div class="container prose">

        <h2>
            O Null Yard
        </h2>

        <p>
            Null Yard jest miejscem
            dla małych eksperymentalnych
            projektów programistycznych.
        </p>

    </div>

</section>

</main>


<footer
    class="site-footer"
    id="contact"
>

    <div class="container">

        <p>
            © 2026 Null Yard
        </p>

    </div>

</footer>

</body>

</html>

CSS

@layer reset, base, layout, components;


@layer reset {

    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }

    body {
        margin: 0;
    }

    img,
    svg,
    picture {
        display: block;
        max-width: 100%;
    }

    button,
    input,
    textarea,
    select {
        font: inherit;
    }

}


@layer base {

    :root {

        --color-bg:
            #f7f7f7;

        --color-surface:
            #ffffff;

        --color-text:
            #161616;

        --color-muted:
            #666666;

        --color-primary:
            #4058d6;

        --color-border:
            #dddddd;

        --space-small:
            0.5rem;

        --space-medium:
            1rem;

        --space-large:
            2rem;

        --space-xl:
            4rem;

        --radius:
            0.75rem;
    }


    body {

        min-height:
            100dvh;

        font-family:
            system-ui,
            sans-serif;

        font-size:
            1rem;

        line-height:
            1.6;

        background:
            var(--color-bg);

        color:
            var(--color-text);
    }


    h1,
    h2,
    h3 {

        line-height:
            1.15;

        text-wrap:
            balance;
    }


    h1 {

        font-size:
            clamp(
                2.5rem,
                7vw,
                6rem
            );

        max-width:
            15ch;

        margin-block:
            0 1.5rem;
    }


    h2 {

        font-size:
            clamp(
                2rem,
                4vw,
                3rem
            );
    }


    a {

        color:
            var(--color-primary);

        text-underline-offset:
            0.2em;
    }


    :focus-visible {

        outline:
            3px solid
            var(--color-primary);

        outline-offset:
            4px;
    }

}


@layer layout {

    .container {

        width:
            min(
                100% - 2rem,
                1200px
            );

        margin-inline:
            auto;
    }


    .header-inner {

        min-height:
            70px;

        display:
            flex;

        align-items:
            center;

        justify-content:
            space-between;

        gap:
            2rem;
    }


    .project-grid {

        display:
            grid;

        grid-template-columns:
            repeat(
                auto-fit,
                minmax(
                    min(250px, 100%),
                    1fr
                )
            );

        gap:
            1.5rem;
    }

}


@layer components {

    .site-header {

        border-block-end:
            1px solid
            var(--color-border);

        background:
            var(--color-surface);
    }


    .logo {

        color:
            inherit;

        font-weight:
            800;

        text-decoration:
            none;
    }


    .navigation {

        display:
            flex;

        flex-wrap:
            wrap;

        gap:
            1.5rem;

        a {

            color:
                inherit;

            text-decoration:
                none;

            &:hover {
                color:
                    var(--color-primary);
            }

        }

    }


    .hero {

        padding-block:
            clamp(
                5rem,
                12vw,
                10rem
            );
    }


    .hero-label {

        color:
            var(--color-primary);

        font-weight:
            700;

        text-transform:
            uppercase;

        letter-spacing:
            0.1em;
    }


    .hero-description {

        max-width:
            50ch;

        font-size:
            1.2rem;

        color:
            var(--color-muted);

        margin-block-end:
            2rem;
    }


    .button {

        display:
            inline-block;

        padding:
            0.8em 1.4em;

        border-radius:
            0.5rem;

        background:
            var(--color-primary);

        color:
            white;

        font-weight:
            700;

        text-decoration:
            none;

        transition:
            transform 150ms ease;

        &:hover {

            transform:
                translateY(-2px);

        }

    }


    .projects {

        padding-block:
            var(--space-xl);
    }


    .section-header {

        margin-block-end:
            2rem;
    }


    .card {

        padding:
            1.5rem;

        border:
            1px solid
            var(--color-border);

        border-radius:
            var(--radius);

        background:
            var(--color-surface);

        h3 {

            margin-block-start:
                0;

        }

    }


    .about {

        padding-block:
            var(--space-xl);
    }


    .prose {

        max-width:
            70ch;
    }


    .site-footer {

        margin-block-start:
            4rem;

        padding-block:
            2rem;

        border-block-start:
            1px solid
            var(--color-border);

        color:
            var(--color-muted);
    }

}

29. Dobre praktyki

Używaj semantycznego HTML

Dobrze:

<nav>
<main>
<article>
<button>

Zamiast:

<div class="nav">
<div class="main">
<div class="article">
<div class="button">

Nie koduj layoutu w HTML

Źle:

<br>
<br>
<br>

dla uzyskania odstępu.

Lepiej:

margin-block: 3rem;

Nie używaj tabel do layoutu

Tabela służy do danych.

Układ twórz przez:

display: grid;

lub:

display: flex;

Nie ustawiaj wszystkiego absolutnie

Układ typu:

position: absolute;
top: 162px;
left: 534px;

bardzo łatwo się rozpada.

Podstawa:

  • normal flow,
  • Flexbox,
  • Grid.

Nie ustawiaj wysokości bez potrzeby

Źle:

.card {
    height: 400px;
}

jeśli zawartość może mieć różną długość.

Nie projektuj dla jednej rozdzielczości

Strona może działać na:

  • telefonie,
  • tablecie,
  • laptopie,
  • dużym monitorze,
  • połowie monitora,
  • przy powiększeniu 200%.

Nie przesadzaj z breakpointami

Najpierw sprawdź:

auto-fit
minmax()
clamp()
flex-wrap
container queries

Nie usuwaj focusa

Źle:

*:focus {
    outline: none;
}

Nie buduj wszystkiego JavaScriptem

HTML i CSS potrafią dziś bardzo dużo.

HTML:

details
summary
dialog
required
pattern
loading="lazy"

CSS:

Grid
Flexbox
:has()
sticky
container queries
animations

30. Ściąga

HTML

<html>
<head>
<body>

<header>
<nav>
<main>
<section>
<article>
<aside>
<footer>

<h1> ... <h6>
<p>
<strong>
<em>

<a>

<ul>
<ol>
<li>

<img>
<picture>
<figure>
<figcaption>

<table>
<thead>
<tbody>
<tr>
<th>
<td>

<form>
<label>
<input>
<textarea>
<select>
<option>
<button>
<fieldset>
<legend>

<details>
<summary>

<div>
<span>

CSS

color
background

font-family
font-size
font-weight
line-height

width
max-width
min-width

height
min-height

margin
padding

border
border-radius

display

flex
grid

gap

position

overflow

aspect-ratio
object-fit

transform
transition
animation

@media
@container
@layer

var()
calc()
min()
max()
clamp()

:is()
:where()
:not()
:has()

:hover
:focus-visible
:nth-child()

::before
::after

31. Co trzeba umieć

Żeby móc powiedzieć „umiem HTML i CSS”, nie trzeba znać wszystkich właściwości na pamięć.

Trzeba rozumieć:

  1. strukturę dokumentu HTML,
  2. semantykę,
  3. linki i obrazy,
  4. formularze,
  5. podstawy dostępności,
  6. selektory CSS,
  7. kaskadę,
  8. specificity,
  9. box model,
  10. jednostki,
  11. typografię,
  12. normal flow,
  13. Flexbox,
  14. Grid,
  15. pozycjonowanie,
  16. responsywność,
  17. media queries,
  18. custom properties,
  19. animacje,
  20. container queries,
  21. clamp(),
  22. :has(),
  23. CSS nesting,
  24. @layer.

Najważniejsza umiejętność to nie pamiętanie całej składni, lecz rozpoznawanie rodzaju problemu.

Przykładowo:

To układ dwuwymiarowy - użyję Grid.

To układ w jednym rzędzie - użyję Flexbox.

Komponent powinien reagować na własną szerokość - użyję container query.

Rozmiar tekstu ma płynnie skalować się z ekranem - użyję clamp().


Mentalny model tworzenia strony

treść
   ↓
semantyczny HTML
   ↓
normal flow
   ↓
Grid / Flexbox
   ↓
responsywne wartości
min() / max() / clamp()
   ↓
media queries tylko tam,
gdzie są naprawdę potrzebne
   ↓
container queries
dla niezależnych komponentów
   ↓
CSS variables
dla wspólnych wartości
   ↓
@layer
dla kontroli kaskady
   ↓
nesting
dla czytelności komponentów
   ↓
JavaScript tylko tam,
gdzie potrzebna jest logika

HTML powinien nadal mieć sens po wyłączeniu CSS.

CSS powinien prezentować dobrze zaprojektowaną strukturę HTML, a nie naprawiać jej błędy.

To jest bardzo dobra baza do tworzenia nowoczesnych stron bez Bootstrapa, Tailwinda czy frameworków JavaScriptowych.

Oficjalne źródła

  • HTML Living Standard: https://html.spec.whatwg.org/
  • CSS specifications: https://www.w3.org/Style/CSS/specs.en.html
  • MDN HTML: https://developer.mozilla.org/docs/Web/HTML
  • MDN CSS: https://developer.mozilla.org/docs/Web/CSS