Pseudoklasy :is() i :where() to jedne z najciekawszych nowości w CSS Selectors Level 4, bo pozwalają radykalnie skrócić złożone selektory, ograniczyć kopiuj‑wklej i lepiej panować nad specyficznością – czyli tym, jak łatwo (lub trudno) nadpisać daną regułę. Dzięki nim CSS staje się czytelniejszy, łatwiejszy w utrzymaniu, a spójne stany interakcji (np. focus) są prostsze do wdrożenia i utrzymania, co realnie wspiera dostępność.
Krótkie przypomnienie – pseudoklasy i specyficzność
Pseudoklasy to specjalny rodzaj selektorów prostych, dzięki którym wybieramy elementy na podstawie ich stanu lub innych cech, których nie widać w samym drzewie DOM (np. :hover, :focus, :first-child). Zawsze poprzedza je dwukropek, np. a:hover.
Specyficzność (ang. specificity) określa, jak “silny” jest selektor – czyli który styl wygra, gdy kilka reguł pasuje do tego samego elementu.
W uproszczeniu:
- selektory ID są bardziej specyficzne niż klasy,
- klasy / pseudoklasy / atrybuty są bardziej specyficzne niż selektory tagów,
- im wyższa specyficzność, tym trudniej później nadpisać regułę.
:is() i :where() działają jak “funkcje” w selektorach – przyjmują listę selektorów i dopasowują elementy pasujące do któregokolwiek z nich, ale różnią się tym, jak wpływają na specyficzność.
Co robi pseudoklasa :is()
Definicja i dopasowywanie
:is() (wcześniej znane jako :matches()) to funkcjonalna pseudoklasa, która przyjmuje listę selektorów rozdzielonych przecinkami i dopasowuje elementy pasujące do dowolnego z nich.
Ogólny wzór:
:is(selektor1, selektor2, selektor3) { … }
Każdy element, który spełni którykolwiek z selektorów w nawiasach, zostanie ostylowany tą regułą. Możemy jej używać w dowolnym miejscu selektora – na początku, w środku, na końcu oraz zagnieżdżać ją w innych :is() / :where().
Podstawowy przykład – skracanie długich selektorów
Załóżmy, że w menu nawigacji chcemy ostylować linki niezależnie od tego, czy są:
- zwykłym linkiem,
- przyciskiem,
- elementem o roli linku.
Bez :is():
nav a, nav button, nav [role="link"] {
font-weight: 600;
text-decoration: none;
}
Z :is():
nav :is(a, button, [role="link"]) {
font-weight: 600;
text-decoration: none;
}
Jeden czytelny selektor zamiast trzech prawie identycznych – to dokładnie ten typ problemu, który :is() ma rozwiązać.
Grupowanie stanów interakcji
Bardzo częsty wzorzec:
button:hover,
button:focus-visible,
a.button:hover,
a.button:focus-visible {
outline: 2px solid currentColor;
}
Z :is():
:is(button, a.button):is(:hover, :focus-visible) {
outline: 2px solid currentColor;
}
W jednym miejscu grupujemy typ elementu (button albo a.button) oraz stany interakcji (:hover, :focus-visible).
To nie tylko mniej kodu – trudniej też zapomnieć o którymś stanie, co jest kluczowe dla dostępności.
“Forgiving selector list” – odporność na błędne selektory
:is() przyjmuje tzw. wybaczającą listę selektorów (forgiving selector list). Oznacza to, że jeśli jakiś selektor w nawiasach jest nieprawidłowy lub nieobsługiwany, jest ignorowany, ale reszta listy działa normalnie:
:is(header, main, footer, nav:has(a)) {
padding-inline: 1.5rem;
}
Jeśli przeglądarka nie obsługuje :has(), ten fragment zostanie pominięty z listy, ale style nadal zadziałają dla header, main i footer.
Gdybyśmy napisali to klasycznym grupowaniem:
header, main, footer, nav:has(a) { … }
i :has() byłby nieobsługiwany, cała reguła zostałaby uznana za niepoprawną i odrzucona. To czyni :is() (i :where()) świetnym narzędziem do progressive enhancement.
Specyficzność :is()
Najważniejsza cecha :is():
Specyficzność
:is()jest równa specyficzności najbardziej specyficznego selektora w nawiasach i wlicza się do specyficzności całego selektora.
Przykład:
article :is(h1, h2, h3) {
color: red;
}
h1, h2, h3 to selektory tagów, więc specyficzność listy jest jak dla typu elementu. Cały selektor ma zatem specyficzność zsumowaną z article + :is(h1, h2, h3) (czyli nadal dość niską).
Bardziej wyrazisty przykład:
article :is(h1.title, h2.title) {
color: red;
}
W nawiasach mamy selektory zawierające klasę i tag, więc specyficzność rośnie – i właśnie ten najwyższy poziom specyficzności przechodzi na całą pseudoklasę :is().
Wniosek: :is() może podnieść specyficzność selektora (w porównaniu do prostego article h1), dlatego warto ostrożnie dobierać zawartość nawiasów, aby nie stworzyć trudnego do nadpisania “potwora”.
Co robi pseudoklasa :where()
Ten sam mechanizm dopasowywania
Z punktu widzenia dopasowywania elementów :where() działa identycznie jak :is() – przyjmuje listę selektorów rozdzielonych przecinkami, pasuje do elementów spełniających którykolwiek z nich, można jej używać w dowolnym miejscu selektora i przyjmuje wybaczającą listę selektorów (błędne są ignorowane):
nav :is(a, button, [role="link"]) { … }
nav :where(a, button, [role="link"]) { … }
Różnica kryje się w specyficzności.
Specyficzność :where() – zawsze 0
Najważniejsza cecha :where():
Selektory w nawiasach
:where()nie wnoszą żadnej specyficzności – specyficzność pseudoklasy:where()jest zawsze równa 0.
To pierwsza funkcja selektora w CSS, która w ten sposób “anuluje” specyficzność listy selektorów. Przykład porównawczy:
article :is(h1.title, h2.title) { color: red; }
article :where(h1.title, h2.title) { color: blue; }
W pierwszej regule (:is()) specyficzność jest wyższa (pochodzi z klasy .title), a w drugiej (:where()) niższa, bo cały fragment w nawiasach nie liczy się do specyficzności; liczy się tylko article. Efekt: reguła z :is() będzie trudniejsza do nadpisania, a reguła z :where() – bardzo łatwa.
To sprawia, że :where() idealnie nadaje się do:
- stylów bazowych i resetów, które chcesz łatwo nadpisywać w komponentach,
- globalnych wzorców (np. marginesy nagłówków, padding w formularzach),
- wszystkiego, co świadomie ma mieć jak najniższą specyficzność.
Przykład – reset nagłówków z niską specyficznością
Zamiast klasycznego grupowania:
h1, h2, h3, h4, h5, h6 {
margin: 0 0 .5em;
}
można napisać:
:where(h1, h2, h3, h4, h5, h6) {
margin: 0 0 .5em;
}
Oba selektory dopasują te same elementy, ale druga reguła będzie dużo łatwiejsza do nadpisania, bo ma specyficzność 0. Dzięki temu komponenty (np. “hero”) lub style użytkownika mają większą szansę wygrać – co jest ważne dla dostępności.
Przykład – bazowe style pól formularza
Globalne ujednolicenie wyglądu pól formularza:
form :where(input, select, textarea, button) {
font: inherit;
color: inherit;
}
Wszystkie podstawowe kontrolki dziedziczą typografię, ale pojedyncze komponenty (np. przycisk typu “primary”) łatwo je nadpiszą, bo specyficzność :where() jest zerowa.
:is() vs :where() – podobieństwa i różnice
Podobieństwa
Obie pseudoklasy łączy kilka wspólnych cech:
- ten sam sposób dopasowywania – lista selektorów, element pasuje jeśli spełnia którykolwiek z nich,
- ta sama składnia – nazwa pseudoklasy + nawiasy z listą selektorów,
- ta sama wybaczająca lista selektorów – błędne selektory są ignorowane, reszta działa,
- można je zagnieżdżać i łączyć, np.
:is(header, main) :where(a, button).
Kluczowa różnica – specyficzność
Porównanie najważniejszych aspektów przedstawia poniższa tabela:
| Aspekt | :is() |
:where() |
|---|---|---|
| Dopasowywanie | element spełnia którykolwiek selektor z listy | to samo |
| Składnia | identyczna (:is(selektor1, selektor2, …)) |
identyczna (:where(selektor1, selektor2, …)) |
| Specyficzność | równa najbardziej specyficznemu selektorowi w nawiasach | zawsze 0, lista nie podnosi specyficzności |
| Główne zastosowanie | gdy chcesz uprościć selektory, nie tracąc siły dopasowania | gdy chcesz uprościć selektory i celowo zachować niską specyficzność |
| Ryzyko “walk specyficzności” | łatwo przypadkiem je zwiększyć | praktycznie brak – reguły łatwo nadpisać |
Krótko: :is() stosuj, gdy chcesz usunąć powtórzenia i zarazem utrzymać lub wzmocnić “siłę” selektora; :where() wybierz, gdy chcesz świadomie “osłabić” regułę, by była łatwa do nadpisania.
Praktyczne wzorce: jak :is() i :where() upraszczają selektory
DRY w komponentach – jeden selektor zamiast wielu
Przykład: przyciski w różnych wariantach
Bez :is():
.button-primary, .button-secondary, .button-tertiary {
border-radius: 9999px;
padding-inline: 1.25rem;
}
Z :is():
:is(.button-primary, .button-secondary, .button-tertiary) {
border-radius: 9999px;
padding-inline: 1.25rem;
}
Mniej powtórzeń, jedno źródło prawdy – grupujemy wspólne cechy i minimalizujemy ryzyko rozbieżności.
Dla stanów interakcji:
Bez :is():
.button-primary:hover,
.button-primary:focus-visible,
.button-secondary:hover,
.button-secondary:focus-visible {
box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 30%, transparent);
}
Z :is():
:is(.button-primary, .button-secondary):is(:hover, :focus-visible) {
box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 30%, transparent);
}
:is() eliminuje podwójne powtarzanie tego samego schematu stanów.
Lepsze, spójne stany focus/hover (dostępność)
Użytkownicy korzystają z różnych metod nawigacji:
- mysz / touchpad – stan
:hover, - klawiatura – stan
:focus/:focus-visible, - urządzenia asystujące – często polegają na dobrze widocznym focus.
Łatwo o błąd: poprawiamy :hover, a zapominamy o :focus-visible. Z :is() możemy od razu grupować stany w jednym miejscu:
:is(a, button, [role="button"]):is(:hover, :focus-visible) {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Taki zapis:
- zapewnia spójność wizualną między różnymi metodami wejścia,
- ułatwia egzekwowanie wytycznych WCAG dotyczących widocznego focusu,
- zmniejsza ryzyko, że jakiś stan zostanie pominięty przy refaktorze.
Globalne “ramy” layoutu z :where()
Przykład: sekcje strony
Zamiast:
header, main, footer, aside, section {
padding-inline: 1.5rem;
}
możemy napisać:
:where(header, main, footer, aside, section) {
padding-inline: 1.5rem;
}
Zyskujemy jedno miejsce, gdzie opisujemy “ramy” layoutu oraz niską specyficzność, którą bez problemu nadpiszą style komponentów.
“Odchudzanie” BEM‑owych selektorów
W kodzie BEM częste są konstrukcje:
.card__title,
.card__subtitle,
.card__meta {
margin-bottom: .5rem;
}
Z :is():
.card :is(.card__title, .card__subtitle, .card__meta) {
margin-bottom: .5rem;
}
Mniej powtórzeń i bardzo jasny kontekst (.card) na początku selektora.
A jeśli to mają być jedynie domyślne odstępy, które komponenty mogą zmienić, lepsze będzie:
.card :where(.card__title, .card__subtitle, .card__meta) {
margin-bottom: .5rem;
}
Świadomie obniżamy specyficzność.
Kontrola specyficzności w praktyce
Kiedy użyć :is(), a kiedy :where()
Użyj :is(), gdy:
- chcesz skrócić selektor i jednocześnie zachować lub podbić jego specyficzność,
- tworzysz regułę, która ma być dość “mocna”, np. nadrzędne style komponentu,
- i tak już operujesz na klasach / ID, więc specyficzność jest świadomie wysoka.
Użyj :where(), gdy:
- piszesz globalne style bazowe (typografia, reset list, marginesy nagłówków),
- chcesz zachować styl, ale nie wygrywać “wojen o specyficzność” z komponentami,
- zależy ci, by style użytkownika / przeglądarki miały szansę nadpisać twoje reguły – istotne dla dostępności (np. tryby wysokiego kontrastu, własne arkusze użytkownika).
Przykład – karta artykułu z bazowymi stylami
Bazowe typograficzne style artykułów:
.article :where(h1, h2, h3, h4, h5, h6) {
font-family: system-ui, sans-serif;
margin: 0 0 .75em;
}
Specyficzność jest niska, więc wewnątrz specjalnego komponentu – np. hero – można bez problemu wprowadzić inny wygląd nagłówka:
.article-hero h1 {
font-size: clamp(2.5rem, 4vw, 3.5rem);
margin-bottom: 1rem;
}
Nie potrzebujemy !important i nie walczymy z kaskadą – to efekt przemyślanego użycia :where().
:is() i :where() a dostępność
Choć specyfikacja CSS nie jest bezpośrednio “o dostępności”, sposób, w jaki organizujemy style, ma realny wpływ na doświadczenie użytkowników z niepełnosprawnościami.
Spójne stany interakcji
Dzięki :is() i :where():
- łatwiej zapewnić spójny hover/focus/active dla całych grup elementów,
- łatwiej zarządzać kontrastem i widocznością focusu,
- zmniejsza się ryzyko, że jakiś wariant będzie miał gorszy focus tylko dlatego, że został pominięty w osobnej regule.
Przykład:
main :is(a, button, [role="button"]):is(:hover, :focus-visible) {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Ten selektor jednym ruchem obejmuje wszystkie główne interaktywne elementy w treści – przydatne na stronach z dużą liczbą linków.
Poszanowanie stylów użytkownika
Stosowanie :where() do bazowych stylów o zerowej specyficzności wspiera:
- arkusze użytkownika (user stylesheets),
- tryby wysokiego kontrastu i inne ustawienia systemowe,
- domyślne style UA (User Agent), tam gdzie powinny mieć pierwszeństwo.
Gdy globalne style mają niską specyficzność, użytkownik ma większe szanse dostosować wygląd do własnych potrzeb – zgodnie z duchem WCAG i zasadami projektowania inkluzywnego.
Łatwiejsze utrzymanie – mniej regresji
Kod z mniejszą liczbą powtórzeń:
- jest prostszy do refaktora,
- lepiej przechodzi przeglądy dostępności (łatwiej prześledzić, gdzie zmieniamy focus/kontrast),
- ogranicza “tajemnicze” regresje, gdy modyfikujemy jeden wariant przycisku, a inny zostaje pominięty.
:is() i :where() promują deklaratywne myślenie o grupach elementów, co zwykle przekłada się na stabilniejsze style i mniej błędów wpływających na użytkowników wrażliwych na zmianę kontrastu, brak focusu czy migotanie.
Wsparcie przeglądarek i strategia użycia
Pseudoklasy :is() i :where() są częścią specyfikacji Selectors Level 4 i są już wspierane przez wszystkie nowoczesne przeglądarki (Chrome, Firefox, Safari, Edge w aktualnych wersjach). Starsze przeglądarki, które ich nie implementują (np. Internet Explorer), po prostu ignorują reguły z :is() / :where().
Praktyczna strategia:
- w nowych projektach, nastawionych na współczesne przeglądarki – używaj
:is()i:where()śmiało, - jeśli musisz wspierać starą przeglądarkę – stosuj progressive enhancement,
- kluczowe style (np. widoczne focusy, kontrast) możesz zdefiniować klasycznymi selektorami,
- a
:is()/:where()wykorzystać do udoskonalania i redukcji duplikacji w nowoczesnych przeglądarkach.
W połączeniu z ich “wybaczającą” naturą nadają się też dobrze do stopniowego wdrażania innych nowych selektorów, takich jak :has().
Kilka praktycznych wzorców do skopiowania
Bazowe marginesy tekstu
:where(p, ul, ol, blockquote, figure) {
margin-block: 0 1rem;
}
Spójne style linków w treści
main :where(a) {
text-decoration-thickness: .1em;
text-underline-offset: .2em;
}
Stany interakcji dla wszystkich przycisków i linków‑przycisków
:is(button, input[type="button"], input[type="submit"], a[role="button"]):is(:hover, :focus-visible) {
filter: brightness(1.05);
}
Uproszczone selektory kart
.card :is(h2, h3) {
font-size: 1.25rem;
margin-bottom: .5rem;
}
.card :where(p, ul, ol) {
margin-bottom: .75rem;
}






