Pseudo-klasy i pseudo-elementy w CSS, czyli jak uniknąć pułapki!
Ucząc się CSS dosyć szybko poznajemy selektor :hover
, być może nieco później ::before
i ::after
, ale czy wiesz, że ten pierwszy jest pseudo-klasą, a pozostałe dwa to pseudo-elementy? Nie całkiem? W takim razie należysz do licznego grona, bo nawet doświadczeni deweloperzy i deweloperki mylą te pojęcia. A ponieważ tak łatwo o pomyłkę, pytania o pseudo-klasy i pseudo-elementy często pojawiają się podczas frontendowych rozmów kwalifikacjnych.
Jeżeli nie chcesz już nigdy zastanawiać się co jest pseudo-elementem, a co pseudo-klasą, to ten artykuł jest dla Ciebie. Dowiesz się czym są, co je odróżnia od pozostałych selektorów CSS, jakie podobieństwa i różnice są między nimi, oraz dlaczego tak łatwo je pomylić. Zatem, przejdźmy do konkretów.
Spis treści
Pseudo-klasy i pseudo-elementy kontra reszta selektorów
Zacznijmy od tego, czym zarówno pseudo-klasy, jak i pseudo-elementy różnią się od pozostałych selektorów CSS.
Pozostałe selektory CSS operują na elementach obecnych w drzewie dokumentu. Czyli, mówiąc wprost, obecnych w dokumencie HTML. Pozwalają one na selekcję elementów na podstawie:
- samej nazwy elementu HTML (
p
,img
,a
) - atrybutu, np.
id
lubclass
, albo konkretny atrybut
/* określone elementy HTML */
p { … }
img { … }
a { … }
/* element HTML z ID: id="some-id" */
#some-id { … }
/* elementy HTML z dodaną klasą CSS: class="some-class" */
.some-class { … }
Powyższe selektory są selektorami prostymi (simple selectors). Proste selektory możemy ze sobą łączyć w łańcuchy, tworząc selektory złożone (compound selectors):
/* elementy <a> zawarte wewnątrz elemetów <p>, które mają klasę "some-class" */
p.some-class a { … }
W selektorach złożonych możemy dodatkowo użyć kombinatorów, by uściślić jak elementy mają się do siebie w strukturze drzewa (>,
~
, +
)
/* elementy <ul> będące bezpośrednimi dziećmi elemetów <nav>, mające klasę "main-menu" */
nav.main-menu > ul { … }
/* elementy <p> występującje po elemencie z klasą "break", niekoniecznie bepośrednio po */
.break ~ p { … }
/* elementy <p> występującje bezpośrednio po elemencie <img> */
img + p { … }
We wszystkich tych przypadkach mamy selektory, które wskazują bezpośrednio na elementy drzewa dokumentu. Nawet łącząc je w długie i skomplikowane łańcuchy (co, tak na marginesie, nie jest jest dobrym pomysłem!), wciąż operujemy na nazwach znaczników i ich atrybutach, czyli na rzeczach, które fizycznie istnieją w kodzie HTML strony.
Pseudo-klasy
Spójrzmy teraz na pseudo-klasy. Na przykład tę, o której wspomniałem na początku, czyli :hover
. Dodajemy ją do elementu, któremu chcemy nadać pewien styl w momencie gdy znajduje się na nim wskaźnik myszy:
a:hover { … }
.some-class:hover { … }
Zatem mamy tu selektor, który ma zastosowanie w chwili, gdy element ma określony stan. Stan ten nie jest częścią samego dokumentu, lecz efektem interakcji z użytkownikiem.
Weźmy inny przykład, pseudo-klasę :checked
, która dotyczy zaznaczonych elementów radio (<input type="radio">
), oraz checkbox (<input type="checkbox">
):
input[type=checkbox]:checked { … }
.my-checkbox:checked { … }
Tutaj znów mamy selektor, który ma zastosowanie do elementów, które mają określony stan na skutek interakcji z użytkownikiem. I podobnie jak poprzednio, stan ten nie wynika z samego dokumentu.
Czy nie zaczyna się nam wyłaniać pewien wzorzec?
Spójrzmy jeszcze na kilka innych przykładów pseudo-klas:
/* ostatni element z listy */
li:first-child { … }
/* trzeci element z listy */
li:nth-child(3) { … }
/* wszystkie przyciski, które nie są dezaktywowane */
button:not([disabled])
Dwie pierwsze pseudo-klasy dotyczą elementów zlokalizowanych w określonych miejscach listy. Czyli selekcjonujemy elementy w oparciu o pewną cechę dokumentu, ale nie wyrażoną wprost w samym dokumencie. Nie wyrażoną wprost, bo elementy co prawda znajdują w określonej kolejności na liście, ale same nie posiadają cech (na przykład jakichś specjalnych atrybutów) które mówiły jakie w tej kolejności zajmują miejsce.
Ostatni przykład jest interesujący. Mamy tu funkcyjną pseudo-klasę (functional pseudo-class), która dotyczy wszystkich elementów, które nie pasują do selektora w nawiasie (funkcyjną, bo przyjmuje argument, przez co jest podobna do funkcji). Tym razem selekcjonujemy elementy które nie posiadają pewnej cechy. Cecha o której brak nam chodzi, może wynikać z dokumentu lub stanu (zależy jaki selektor umieścimy w nawiasie), ale nie jest wyrażona wprost w dokumencie. Nie wprost w takim sensie, że cechą tych elementów jest to, iż czegoś nie mają.
Myślę, że staje się coraz bardziej jasne, czym charakteryzują się pseudoklasy: pozwalają na selekcję elementów na podstawie ich cech nie wyrażonych wprost w dokumencie (takich jak położenie elementu na liście) oraz cech wynikających z interakcji z użytkownikiem (jak na przykład zaznaczenie, czy focus).
Oto definicja pseudo-klas wzięta wprost ze specyfikacji:
„Pseudo-klasy to proste selektory, które umożliwiają selekcję na podstawie informacji, które znajdują się poza drzewem dokumentu lub których wyrażenie przy użyciu innych prostych selektorów może być niewygodne lub niemożliwe. Pseudo-klasy mogą być również dynamiczne, w tym sensie, że element może nabyć lub utracić pseudo-klasę, gdy użytkownik wchodzi w interakcję z dokumentem, bez zmiany samego dokumentu. Pseudoklasy nie pojawiają się w źródle lub drzewie dokumentów ani nie modyfikują ich.”
Źródło: https://drafts.csswg.org/selectors/#pseudo-classes
I jak, klarowne? Ta definicja jest dosyć formalna, ale po przejrzeniu wcześniejszych przykładów powinna być zrozumiała.
Pseudo-elementy
Przejdźmy teraz do drugiego tematu tego artykułu, czyli pseudo-elementów. Zacznijmy znów od przykładu wspomnianego na początku, czyli pseudo-elementów ::before
i ::after:
<p>jaśnie</p>
<style>
p: {
color: black;
}
p::before {
content: 'prze';
color: red;
}
p::after {
content: 'nie';
color: red;
}
</style>
Rezultatem tego kodu będzie widoczny w dokumencie wyraz „przejaśnienie”, przy czym sylaby „prze” i „nie” będą miały kolor czerwony:

Pseudo-elementy ::before
i ::after
powodują wstawienie do dokumentu dodatkowej treści (określonej przez właściwość content). Pamiętaj jednak, że dodana w ten sposób treść nie modyfikuje samego dokumentu i nie powoduje pojawienia się nowego elementu w strukturze drzewa. Jest czysto wizualna i dostępna jedynie z poziomu CSS (nie można na przykład dostać się do niej z JavaScript
). Zatem mamy tu przykład pseudo-elementów które pozwalają tworzyć i stylować elementy, które nie istnieją w samym dokumencie. Są to niejako “wirtualne” elementy utworzone przez CSS.
Spójrzmy na inny przykład, pseudo-element :first-letter
:
<p>Lorem ipsum dolor sit amet.</p>
<style>
p::first-letter {
font-size: 2em;
}
</style>
Tym razem efektem działania kodu będzie prezentacja pierwszego znaku, czyli litery „L”, czcionką dwukrotnie większą od reszty zdania:

Zwróć uwagę, że w strukturze dokumentu, litera „L” nie jest samodzielnym elementem, jest po prostu pierwszym znakiem treści paragrafu. To pseudo-element :first-letter
pozwala nam traktować tę literę tak, jakby była niezależnym elementem. Czyli znów mamy „wirtualny” element CSS.
I o to właśnie chodzi w pseudo-elementach, pozwalają one z poziomu CSS stylować fragmenty dokumentu nie będące elementami HTML. Tworzą one abstrakcyjne (wirtualne) elementy, nie modyfikując jednocześnie drzewa dokumentu. Elementy te widoczne są jedynie dla CSS.
Podobieństwa i różnice
Omówiliśmy jak działają pseudo-klasy i pseudo-elementy, czas zobaczyć co z tego wynika podsumowując podobieństwa i różnice pomiędzy nimi.
Podobieństwa:
- Zarówno pseudo-klasy jak i pseudo-elementy reprezentują informacje które nie są bezpośrednio obecne w drzewie dokumentu
- Nie modyfikują źródła ani drzewa dokumentu
Różnice:
- Pseudo-klasy odnoszą się do elementów, które istnieją w dokumencie. Odnoszą się też całości elementu, a nie jego części. Dotyczą jego stanu, lub cech wynikających ze struktury dokumentu, i w tym sensie działają na informacjach nie zadeklarowanych w sposób jawny w drzewie dokumentu. Jednak sam element istnieje.
- Pseudo-elementy, w przeciwieństwie do pseudo-klas, odnoszą się do elementów których faktycznie w dokumencie nie ma. Pseudo-elementy są abstrakcjami istniejącymi jedynie w warstwie CSS. Pozwalają na selekcję fragmentu elementu, lub dodanie nowej treści do istniejącego elementu.
- Różnią się składnią, choć to różnica subtelna:
- Selektory pseudo-klas zaczynają się od pojedynczego dwukropka:
:class-name
. - Selektory pseudo-elementów zaczynają się od podwójnego dwukropka:
::element-name
.
- Selektory pseudo-klas zaczynają się od pojedynczego dwukropka:
To o co chodzi z tą pułapką?
Pewnie zastanawiasz się, gdzie w tym wszystkim jest wspomniana w tytule pułapka. Ano właśnie w składni.
Różnice w składni wymagają dodatkowego wyjaśnienia, bo sprawa nie jest taka oczywista. Rzeczywiście w najnowszych specyfikacjach (Selectors Level 3 oraz Selectors Level 4) wygląda to tak, jak napisałem wyżej: nazwy pseudo-klas zaczynają się od :
, a nazwy pseudo-elementów od ::
. Ale nie zawsze tak było. W starszych wersjach specyfikacji (aż do CSS 2.2) nazwy jednych i drugich zaczynały się od pojedynczego dwukropka: :
. Czyli pseudo-klasy i pseudo-elementy pod względem składni wyglądały identycznie! To z pewnością jedna z głównych przyczyn nieporozumień, pomyłek, oraz trudności z odróżnieniem pseudo-klas i pseudo-elementów.
Gdyby nie było to wystarczająco pogmatwane, to jeszcze przeglądarki zawsze muszą zachować wsteczną kompatybilność. Dlatego nadal wspierają obie składnie. Przynajmniej w odniesieniu do starszych pseudo-elementów, które były obecnie już w CSS 2.2, bo nowo wprowadzane pseudo-elementy mają już wyłącznie składnię z podwójnym dwukropkiem (::
). W materiałach sprzed kilku lat znajdziesz radę, by używać starej składni pseudo-elementów, gdyż ma szersze wsparcie w przeglądarkach. Jednak ten argument mocno się już zdezaktualizował. Obecnie, o ile nie musisz wspierać IE8 (a mam szczerą nadzieję, że nie musisz), nie ma żadnego uzasadnienia dla używania starej składni, zatem zaczynaj pseudo-elementy od podwójnego dwukropka (::
).
Tym sposobem dotarliśmy do końca artykułu. Mam nadzieję, że udało mi się rozjaśnić Ci temat podobieństw i różnic pomiędzy pseudo-klasami i pseudo-elementami. Jeżeli podczas technicznego interview natrafisz na dotyczące tego pytanie, z pewnością odpowiesz bez problemu, unikając pułapki.