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
- HTML - podstawy
- Struktura dokumentu
- Semantyka HTML
- Tekst, listy i linki
- Obrazy i multimedia
- Tabele
- Formularze
- Dostępność
- CSS - podstawy
- Selektory
- Kaskada, dziedziczenie i specificity
- Box model
- Jednostki
- Kolory i zmienne CSS
- Typografia
- Normal flow
- Flexbox
- CSS Grid
- Pozycjonowanie
- Responsywność
- Container Queries
- Nowoczesny CSS
- Dark mode
- Animacje
- Formularze w CSS
- Reset / baza CSS
- Organizacja projektu
- Kompletny przykład strony
- Dobre praktyki
- Ściąga
- 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.
<head>
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>
<header>
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.
<nav>
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>
<footer>
Stopka dokumentu lub sekcji:
<footer>
<p>© 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 a przycisk
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ć:
- strukturę dokumentu HTML,
- semantykę,
- linki i obrazy,
- formularze,
- podstawy dostępności,
- selektory CSS,
- kaskadę,
- specificity,
- box model,
- jednostki,
- typografię,
- normal flow,
- Flexbox,
- Grid,
- pozycjonowanie,
- responsywność,
- media queries,
- custom properties,
- animacje,
- container queries,
clamp(),:has(),- CSS nesting,
@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