Selektory CSS stanowią fundament nowoczesnych stron, umożliwiając precyzyjne wskazywanie elementów HTML i nadawanie im stylów. Umiejętność łączenia różnych typów selektorów daje pełną kontrolę nad wyglądem i zachowaniem interfejsu. Zrozumienie ich działania przekłada się na czytelniejszy, łatwiejszy w utrzymaniu i wydajniejszy kod, co bezpośrednio poprawia jakość produktu i doświadczenie użytkownika.
Fundamenty selektorów CSS i ich znaczenie w stylowaniu
Selektor CSS to wzorzec określający zbiór elementów HTML, do których zastosowana zostanie dana reguła. To on łączy strukturę HTML z deklaracjami CSS, decydując, które elementy zostaną sformatowane.
Znaczenie selektorów wykracza poza wygląd. Właściwy dobór selektorów wpływa na wydajność, utrzymywalność, czytelność i skalowanie projektu. Każdy selektor ma swoją specyficzność – mechanizm rozstrzygania konfliktów, gdy wiele reguł dotyczy tego samego elementu. Zrozumienie specyficzności wyjaśnia, dlaczego niektóre style nie działają zgodnie z oczekiwaniami.
Podstawowe selektory – budulec stylizacji
Najczęściej używane typy selektorów to:
- selektor elementu – wskazuje wszystkie elementy danego typu (np. p, h1),
- selektor klasy – wybiera elementy z przypisaną klasą poprzedzoną kropką (np. .btn),
- selektor identyfikatora – wybiera element o unikalnym id poprzedzonym # (np. #header),
- selektor uniwersalny – wskazuje wszystkie elementy w dokumencie (*).
Selektor elementu – fundament stylizacji
Selektor elementu wybiera wszystkie wystąpienia danego tagu i nadaje im spójny styl. Proste przykłady użycia:
p { color: blue; font-size: 16px; }
h1 { color: #333; margin-bottom: 20px; }
Specyficzność selektora elementu wynosi 0-0-1, więc jest łatwo nadpisywalny przez klasy i identyfikatory. Selektor uniwersalny * ma specyficzność 0-0-0 i na dużych stronach może negatywnie wpływać na wydajność.
Selektor klasy – elastyczność i wielokrotne zastosowanie
Klasy pozwalają selektywnie stylizować elementy niezależnie od ich tagu. Przykłady definicji klas:
.nawigacja { background-color: #333; padding: 10px; }
.przycisk { background-color: #007bff; color: white; padding: 10px 20px; }
Jeden element może mieć wiele klas, dzięki czemu styl jest modularny i wielokrotnego użytku. Specyficzność selektora klasy wynosi 0-1-0 – to rozsądny kompromis między siłą a elastycznością.
Selektor identyfikatora – precyzja i unikatowość
Identyfikatory służą do wskazywania pojedynczych, unikalnych elementów. Przykłady użycia:
#gora-strony { padding: 20px; background-color: #f0f0f0; }
#glowna-zawartosc { max-width: 1200px; margin: 0 auto; }
Specyficzność selektora identyfikatora wynosi 1-0-0, dlatego należy stosować go oszczędnie. W praktyce identyfikatory lepiej rezerwować dla JavaScript i kotwic, a do stylowania preferować klasy.
Zaawansowane selektory – precyzja na nowym poziomie
Selektory atrybutów – elastyczne dopasowywanie
Selektory atrybutów wybierają elementy na podstawie obecności i wartości atrybutów. Proste dopasowanie atrybutu (bez względu na wartość):
a[target] { color: red; }
input[disabled] { opacity: 0.5; }
Dokładne dopasowanie wartości atrybutu:
input[type="text"] { border: 1px solid #ccc; }
a[rel="nofollow"] { opacity: 0.8; }
Dopasowywanie fragmentów wartości atrybutów:
a[href^="http://"] { color: #007bff; }
a[href$=".pdf"] { background-image: url(pdf-icon.png); }
img[alt*="logo"] { border: 2px solid gold; }
To świetny sposób na stylizację bez dodawania nadmiarowych klas do HTML.
Pseudoklasy – reagowanie na stan i interakcję
Pseudoklasy reprezentują stan lub kontekst elementu (np. interakcje, pozycję w drzewie, walidację formularzy). Najczęstsze grupy pseudoklas to:
- interaktywne – :hover, :focus, :active, :visited,
- strukturalne – :first-child, :last-child, :nth-child(),
- formularzy – :required, :optional, :disabled, :enabled, :valid, :invalid, :read-only, :read-write.
Przykłady dla stanu i interakcji:
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; text-decoration: underline; }
a:active { color: darkred; }
input:focus { outline: 2px solid #007bff; background-color: #f9f9f9; }
Przykłady pseudoklas strukturalnych:
li:first-child { font-weight: bold; }
li:last-child { border-bottom: none; }
tr:nth-child(even) { background-color: #f2f2f2; }
li:nth-child(3n) { color: red; }
Przykłady dla stanów formularzy:
input:required { box-shadow: 0 0 5px rgba(255, 0, 0, 0.5); }
input:disabled { background-color: #e9e9e9; cursor: not-allowed; }
input:valid { border-color: green; }
input:invalid { border-color: red; }
Zaawansowane pseudoklasy otwierają nowe możliwości selekcji:
.parent :not(p:nth-last-child(1)) { color: blue; }
.parent:has([type="checkbox"]:checked) p { color: red; }
:is(.header, .footer) a { color: inherit; }
:not() wyklucza dopasowania, :has() stylizuje element na podstawie jego zawartości, a :is() i :where() upraszczają złożone selektory (z różnicą w specyficzności).
Pseudoelementy – tworzenie i stylizowanie wirtualnych elementów
Pseudoelementy pozwalają stylizować fragmenty treści lub wstawiać dodatkową zawartość. Przykłady formatowania tekstu:
p::first-line { font-weight: bold; color: #333; }
p::first-letter { font-size: 2em; font-weight: bold; }
Wstawianie dekoracyjnej treści przed/po elemencie:
h3::before { content: "→ "; color: #007bff; }
.quote::before { content: open-quote; }
.quote::after { content: close-quote; }
Warto znać także ::selection, ::marker, ::placeholder oraz stan :placeholder-shown.
Kombinatory – łączenie selektorów dla większej precyzji
Najważniejsze kombinatory i ich relacje:
- potomka (spacja) – dowolny zagnieżdżony element w obrębie innego,
- dziecka (>) – bezpośredni potomek elementu,
- sąsiada (+) – bezpośrednio następujący element rodzeństwa,
- ogólny sąsiad (~) – dowolny późniejszy element rodzeństwa.
Kombinator potomka – selektor przestrzeni
Przykłady użycia kombinatora potomka:
ul li { color: green; }
.container p { font-size: 16px; }
div em { color: red; }
Specyficzność łączy się sumarycznie z poszczególnych części selektora.
Kombinator dziecka – selektor bezpośredniego potomka
Przykłady użycia znaku >:
div > p { font-size: 18px; }
ul > li { list-style: none; }
.parent > .child { margin: 0; }
div p wybierze każdy p wewnątrz div, a div > p tylko bezpośrednie dzieci.
Kombinator sąsiada – selektor bezpośredniego następnika
Przykłady użycia znaku +:
h2 + p { margin-top: 0; }
input + button { margin-left: 10px; }
.title + .description { color: gray; }
Ogólny kombinator sąsiada – selektor wszystkich następców
Przykłady użycia znaku ~:
h2 ~ p { color: gray; }
.active ~ li { display: none; }
input[type="checkbox"]:checked ~ .result { visibility: visible; }
Specyficzność CSS i kaskada – zasady i hierarchia
Zrozumienie specyficzności
Specyficzność zapisujemy jako trzy liczby A-B-C, gdzie: A = liczba identyfikatorów, B = liczba klas/atrybutów/pseudoklas, C = liczba typów i pseudoelementów.
Dla szybkiej orientacji porównaj przykłady:
| Selektor | Specyficzność |
|---|---|
| #header .nav li:hover | 1-2-1 |
| .button:focus | 0-2-0 |
| p | 0-0-1 |
| * | 0-0-0 |
Selektory o wyższej specyficzności wygrywają z mniej specyficznymi, dlatego zbyt „mocne” selektory utrudniają późniejsze nadpisywanie stylów.
Kaskada – zasady stosowania stylów
Kaskada określa, które deklaracje zostaną zastosowane do elementu. Priorytety można ująć następująco:
- specyficzność – selektor bardziej specyficzny wygrywa;
- kolejność w źródle – przy remisie wygrywa reguła późniejsza;
- waga – deklaracje z !important mają pierwszeństwo.
Style inline (atrybut style) mają bardzo wysoką skuteczność, a !important przebija większość zasad. Stosuj !important wyłącznie w ostateczności – komplikuje on utrzymanie kodu.
Najlepsze praktyki w stosowaniu selektorów CSS
Wolność od identyfikatorów w CSS
Unikaj identyfikatorów w arkuszach stylów – ich wysoka specyficzność utrudnia nadpisywanie i konserwację. Do stylowania preferuj klasy, a id rezerwuj dla JavaScript i kotwic.
Prostota selektorów
Prostsze selektory są szybsze, jaśniejsze i bardziej elastyczne. Zobacz porównanie:
/* Źle – zbyt skomplikowane */
body div.container div.row div.col-md-4 p.text { color: blue; }
/* Dobrze – prosto i elastycznie */
.text { color: blue; }
Minimalizuj zagnieżdżenia – łatwiej wtedy nadpisywać reguły i utrzymać spójność.
Metodologia BEM
BEM porządkuje klasy w modelu Block–Element–Modifier. Przykładowy schemat komponentu:
/* Block */
.card { }
/* Element */
.card__header { }
.card__body { }
.card__footer { }
/* Modifier */
.card--highlighted { }
.card__body--dark { }
BEM znacząco zmniejsza konflikty, zwiększa modularność i czytelność arkuszy.
Unikanie długich selektorów
Długie, mocno zagnieżdżone selektory są kruche i wolniejsze. W praktyce lepiej opierać się na klasach:
/* Źle – zależne od struktury HTML */
header nav ul li a { color: blue; }
/* Dobrze – niezależne od struktury */
.nav-link { color: blue; }
Odporność na zmiany struktury HTML przyspiesza rozwój i refaktoryzację.
Wydajność selektorów CSS
Ponowne obliczanie stylów
Przeglądarki dopasowują selektory do elementów DOM. Na rozbudowanych stronach koszt tej operacji rośnie. Uproszczenie selektorów i preferowanie klas przyspiesza dopasowywanie oraz renderowanie.
Uniwersalny selektor i wydajność
Unikaj nadużywania selektora *. Lepsze alternatywy:
/* Źle – działa wolniej */
* { margin: 0; padding: 0; }
/* Lepiej – szybciej */
html, body, h1, p, div { margin: 0; padding: 0; }
/* Najlepiej – tylko to, czego naprawdę potrzebujesz */
body { margin: 0; }
Drobne nieoptymalności kumulują się na dużych projektach – dbaj o selektory od początku.
Modularność CSS i wydajność
Dzielenie arkuszy na moduły oraz warunkowe ładowanie przez media queries ogranicza koszty. Przykłady linkowania:
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">
Praktyczne zastosowania i przykłady
Stylizowanie formularzy
Selektory atrybutów i pseudoklasy formularzy umożliwiają kontekstowe stylizowanie pól:
/* Wszystkie pola tekstowe */
input[type="text"] { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
/* Wyróżnienie pól wymaganych */
input:required { border-color: #ff6b6b; box-shadow: 0 0 5px rgba(255, 107, 107, 0.3); }
/* Style dla zaznaczonych checkboxów */
input[type="checkbox"]:checked + label { font-weight: bold; color: #007bff; }
/* Wyłączone pola */
input:disabled { background-color: #e9e9e9; cursor: not-allowed; }
/* Pola w fokusie */
input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); }
Tabelaryczne style
Pseudoklasa :nth-child() ułatwia naprzemienne wzory i precyzyjne dopasowania w tabelach:
/* Naprzemienne wiersze */
tr:nth-child(even) { background-color: #f2f2f2; }
/* Pierwsza kolumna */
td:first-child { font-weight: bold; width: 150px; }
/* Ostatnia kolumna */
td:last-child { text-align: right; }
/* Efekt najechania na wiersz */
tr:hover { background-color: #e0e0e0; }
/* Wiersz nagłówka */
thead tr { background-color: #333; color: white; }
thead tr:hover { background-color: #555; }
Kombinatory i pseudoklasy świetnie sprawdzają się w nawigacji:
/* Główne elementy menu */
.nav > li { display: inline-block; padding: 0 15px; }
/* Linki w menu */
.nav a { color: #333; text-decoration: none; }
/* Efekt najechania na link */
.nav a:hover { color: #007bff; border-bottom: 2px solid #007bff; }
/* Aktywna pozycja menu */
.nav li.active a { color: #007bff; border-bottom: 2px solid #007bff; }
/* Menu rozwijane */
.nav li:hover ul { display: block; }
.nav ul { display: none; position: absolute; left: 0; top: 100%; background-color: white; border: 1px solid #ccc; }
.nav ul li { display: block; padding: 10px 15px; }
Zaawansowane techniki wyboru elementów
Selektory relatywne i :has()
Pseudoklasa :has() pozwala stylizować element na podstawie jego potomków lub sąsiadów:
form:has(input[type="checkbox"]:checked) { border: 2px solid green; }
p:has(+ img) { margin-bottom: 0; }
div:has(.error) { border-color: red; }
label:has(input:checked) { font-weight: bold; color: #007bff; }
Pseudoklasy :is() i :where()
:is() i :where() upraszczają złożone selektory, przy czym :where() ma zawsze specyficzność 0:
/* Zamiast */
.header a:hover, .footer a:hover, .nav a:hover { color: #007bff; }
/* Można napisać */
:is(.header, .footer, .nav) a:hover { color: #007bff; }
:where(.header, .footer, .nav) a:hover { color: #007bff; }
Pseudoklasa :not()
:not() wyklucza elementy pasujące do danego selektora. Przykłady użycia:
li:not(:last-child) { border-bottom: 1px solid #ccc; }
input:not([type="submit"]) { padding: 8px; }
p:not(:first-child) { margin-top: 15px; }
a:not(.special) { color: blue; }
Zalecenia i najlepsze praktyki dla przyszłości
Preferuj klasy zamiast identyfikatorów, unikaj przesadnej złożoności selektorów, stosuj spójną metodologię nazewnictwa (np. BEM), dbaj o modularność i regularnie optymalizuj wydajność.
Upraszczaj selektory, minimalizuj i dziel CSS na moduły, a wydajność monitoruj w narzędziach deweloperskich – wskazują one wolne reguły i miejsca do optymalizacji.
Świadome zarządzanie specyficznością i kaskadą to klucz do elastycznego, łatwego w utrzymaniu CSS – utrzymuj niską specyficzność, by reguły dawały się łatwo nadpisywać.