Warunkowe klasy w React - kiedy użyć clsx i CSS Modules

Bruno Krawczyk 3 czerwca 2026
Jak efektywnie zarządzać klasami CSS w React, stosując `react conditional classname`. Andrew Ezeani.

Spis treści

W interfejsie React stan komponentu często powinien zmieniać wygląd przycisku, karty, formularza albo elementu listy. Pokażę, jak warunkowo przypisywać klasy CSS, kiedy wystarczy operator trójargumentowy, a kiedy lepiej użyć clsx, CSS Modules lub mapy wariantów.

Najważniejsze zasady warunkowych klas w React

  • className przyjmuje tekst, dlatego warunek trzeba zamienić na poprawny ciąg klas.
  • Operator trójargumentowy sprawdza się przy prostym wyborze między dwiema klasami.
  • clsx porządkuje kilka warunków i automatycznie pomija wartości fałszywe.
  • CSS Modules wymagają używania nazw z obiektu importowanego przez komponent.
  • Klasa CSS nie zastępuje atrybutów dostępności, takich jak disabled, aria-pressed czy aria-invalid.

Jak warunkowo przypisywać klasy w React

W JSX atrybut className działa podobnie do HTML-owego class, ale jego wartość może pochodzić z JavaScriptu. Dzięki temu wygląd elementu może zależeć od stanu komponentu, właściwości lub wyniku funkcji.

Najprostszy przypadek to wybór jednej z dwóch klas za pomocą operatora trójargumentowego:

function Button({ isPrimary }) {
  return (
    
  );
}

Jeśli isPrimary ma wartość true, React doda klasę button--primary. W przeciwnym razie użyje wariantu drugorzędnego. To rozwiązanie jest czytelne, gdy istnieje jeden warunek i dwie możliwe wartości.

Można też połączyć klasę stałą z warunkową interpolacją:

function Card({ isSelected }) {
  return (
    
Zawartość karty
); }

Ten zapis jest poprawny, ale przy większej liczbie warunków szybko staje się mało wygodny. W praktyce staram się nie budować długich szablonów tekstowych, gdy komponent ma trzy lub cztery niezależne stany.

Operator trójargumentowy, operator && i tablica klas

Operator && jest kuszący, gdy chcemy dodać klasę tylko w jednym przypadku:

W tym przykładzie trzeba uważać. Gdy warunek jest fałszywy, wartość className może nie być tekstem, którego oczekuje komponent. Bezpieczniej użyć wartości domyślnej:

Jeszcze częstszy błąd pojawia się przy interpolacji:

Karta

Gdy isSelected jest fałszywe, do tekstu może trafić słowo false. Przeglądarka zwykle nie pokaże przez to awarii, ale otrzymamy niepoprawny ciąg klas. Przy jednym warunku wybieram operator trójargumentowy, a przy kilku korzystam z tablicy albo biblioteki pomocniczej.

Tablica z filter(Boolean) pozwala zbudować klasy bez dodatkowej zależności:

function Alert({ type, isDismissible }) {
  const className = [
    'alert',
    type === 'error' && 'alert--error',
    type === 'success' && 'alert--success',
    isDismissible && 'alert--dismissible'
  ]
    .filter(Boolean)
    .join(' ');

  return 
Komunikat
; }

To podejście jest proste i dobrze nadaje się do małych komponentów. Jego wadą jest powtarzalny kod, szczególnie gdy klasy zależą od wielu wariantów. Wtedy lepiej przenieść składanie klas do narzędzia, które robi to za nas.

Dlaczego clsx ułatwia pracę z warunkami

clsx to niewielka funkcja, która łączy klasy tekstowe, obiekty i tablice, pomijając wartości fałszywe. Instalacja wygląda standardowo:

npm install clsx

Najprostszy przykład użycia wygląda tak:

import clsx from 'clsx';

function Button({ isPrimary, isDisabled }) {
  return (
    
  );
}

W tym zapisie każda klasa ma jasno określone znaczenie. Wartość false, null lub undefined nie zostanie dodana, więc nie trzeba ręcznie czyścić wyniku. Ja najczęściej używam formy obiektowej, gdy komponent ma kilka niezależnych stanów:

const buttonClassName = clsx('button', {
  'button--primary': variant === 'primary',
  'button--secondary': variant === 'secondary',
  'button--loading': isLoading,
  'button--full-width': fullWidth
});

Obiekt dobrze pokazuje relację między warunkiem a klasą. Dzięki temu łatwiej zauważyć, że stan ładowania i szerokość przycisku są niezależne od jego wariantu. Przy pojedynczym warunku dodatkowa paczka nie jest potrzebna, ale w większym projekcie spójność zapisu często jest warta tej małej zależności.

Jeśli komponent przyjmuje własne klasy z zewnątrz, dodaj je do tego samego wywołania:

function Panel({ className, isCompact }) {
  return (
    
Zawartość
); }

W ten sposób komponent zachowuje klasę bazową, obsługuje swój stan i pozwala rodzicowi dodać własne modyfikatory. Trzeba tylko ustalić w zespole, czy klasy z className mają być dodawane przed czy po klasach wariantu, bo kolejność może mieć znaczenie przy konfliktach CSS.

CSS Modules i warunkowe klasy bez konfliktów nazw

Przy CSS Modules nazwy klas są lokalne dla pliku i zwykle zostają przekształcone podczas budowania aplikacji. Nie wpisujemy więc bezpośrednio tekstu button--active, tylko korzystamy z obiektu styles:

import styles from './Button.module.css';

function Button({ isActive }) {
  return (
    
  );
}

Przy kilku klasach można użyć clsx razem z CSS Modules:

import clsx from 'clsx';
import styles from './Card.module.css';

function Card({ isSelected, isDisabled }) {
  return (
    
Treść
); }

Składnia z nawiasami kwadratowymi jest tu ważna, ponieważ wartość styles.selected jest dynamiczną nazwą właściwości. Najczęstszy błąd polega na zapisaniu { selected: isSelected }, co nie zadziała tak, jak oczekujemy. Przy CSS Modules pilnuję też, aby każda klasa używana w komponencie faktycznie istniała w pliku CSS, bo literówka nie zawsze jest od razu widoczna w interfejsie.

Warianty komponentów, Tailwind i większe interfejsy

Gdy komponent ma kilka oficjalnych wariantów, lepiej opisać je mapą niż tworzyć długi łańcuch operatorów trójargumentowych. Przykład dla przycisku:

const variantClasses = {
  primary: 'button button--primary',
  secondary: 'button button--secondary',
  danger: 'button button--danger'
};

function Button({ variant = 'primary', isLoading }) {
  const className = clsx(variantClasses[variant], {
    'button--loading': isLoading
  });

  return (
    
  );
}

Mapa centralizuje dozwolone warianty i ogranicza ryzyko literówek. W aplikacji TypeScript można dodatkowo zawęzić variant do unii wartości, dzięki czemu kompilator zgłosi nieobsługiwany wariant jeszcze przed uruchomieniem aplikacji.

W Tailwind CSS warunkowe klasy również można budować za pomocą clsx:

const className = clsx(
  'rounded px-4 py-2 font-semibold',
  isPrimary ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-900',
  isDisabled && 'cursor-not-allowed opacity-50'
);

Nie warto jednak tworzyć klas Tailwind przez składanie fragmentów tekstu, na przykład bg-${color}-500. Narzędzia analizujące kod mogą nie wykryć takiej nazwy podczas budowania CSS. Bezpieczniejsza jest jawna mapa pełnych klas:

const backgroundByColor = {
  blue: 'bg-blue-500',
  green: 'bg-green-500',
  red: 'bg-red-500'
};

const className = backgroundByColor[color] ?? backgroundByColor.blue;

To rozwiązanie jest odrobinę dłuższe, ale przewidywalne. W dużych interfejsach przewidywalność wygrywa z pozorną oszczędnością kilku znaków.

Najczęstsze błędy przy zmianie klas

Warunkowe klasy są proste, dopóki logika wyglądu nie zaczyna zastępować logiki komponentu. Zwracam szczególną uwagę na kilka problemów, które regularnie pojawiają się w kodzie produkcyjnym.

  • Używanie class zamiast className w JSX. React oczekuje atrybutu className.
  • Wstawianie wartości boolean do template stringa, co może wygenerować tekst false lub true.
  • Łączenie sprzecznych klas, na przykład text-red-500 text-green-500, bez jasnej reguły, która ma wygrać.
  • Ukrywanie istotnego stanu wyłącznie za pomocą CSS. Wygląd przycisku nie zastępuje atrybutu disabled.
  • Tworzenie zbyt długich wyrażeń w JSX. Jeśli warunek zajmuje kilka linii, warto obliczyć klasę wcześniej.

Przykładowo zaznaczenie zakładki powinno zmieniać nie tylko kolor, lecz także stan dostępności:

function Tab({ isActive, onClick, children }) {
  return (
    
  );
}

Klasa odpowiada tu za prezentację, a aria-selected przekazuje informację technologiom asystującym. Nie traktuję stylu jako źródła prawdy o stanie. Stan powinien wynikać z propsów lub danych komponentu, a klasy mają go tylko odzwierciedlać.

Jeśli warunków robi się dużo, rozważam też rozdzielenie komponentu na mniejsze części. Biblioteka do łączenia klas nie naprawi nieczytelnego modelu stanu. Może jedynie sprawić, że jego wizualna reprezentacja będzie łatwiejsza do utrzymania.

Jak wybrać właściwe rozwiązanie w praktyce

Przypadek Najlepszy wybór Dlaczego
Jedna klasa zależna od jednego warunku Operator trójargumentowy Najmniej kodu i pełna czytelność
Kilka niezależnych stanów clsx Łatwe łączenie klas bez wartości false
Brak dodatkowych zależności Tablica i filter(Boolean) Rozwiązanie oparte wyłącznie na JavaScript
Style lokalne dla komponentu CSS Modules z clsx Bezpieczne nazwy generowane przez system modułów
Wiele ustalonych wariantów Mapa wariantów Jedno miejsce z dozwolonymi klasami

Moja praktyczna reguła jest prosta. Dla jednego wyboru używam operatora trójargumentowego, dla kilku warunków sięgam po clsx, a dla większej liczby wariantów tworzę mapę. Dzięki temu sposób zapisu wynika z rzeczywistej złożoności komponentu, a nie z przyzwyczajenia.

Niezależnie od wybranej metody warto sprawdzić trzy rzeczy. Klasa bazowa powinna pozostać obecna, każda kombinacja stanów powinna dawać przewidywalny wynik, a zmiana wyglądu nie może pogarszać obsługi klawiaturą ani komunikacji z czytnikiem ekranu.

Mała decyzja w JSX, duża różnica w utrzymaniu interfejsu

Warunkowe przypisywanie klas w React nie wymaga skomplikowanej abstrakcji. Najważniejsze jest dopasowanie narzędzia do sytuacji i unikanie konstrukcji, które przypadkiem dodają do DOM tekst false, mieszają sprzeczne warianty albo ukrywają prawdziwy stan komponentu.

Na początku wystarczy czysty JavaScript, lecz wraz z rozwojem aplikacji dobrze uporządkowane klasy, mapa wariantów i konsekwentne użycie clsx wyraźnie poprawiają czytelność. To drobny element kodu, który przycisk po przycisku decyduje o tym, czy interfejs da się spokojnie rozwijać.

FAQ - Najczęstsze pytania

Operator trójargumentowy sprawdza się przy jednym warunku i wyborze między dwiema klasami. Przy kilku niezależnych stanach, takich jak wariant, ładowanie i szerokość przycisku, wygodniejszy jest clsx. Bez dodatkowej zależności można też użyć tablicy z filter(Boolean).join(' ').

Nie należy bezpośrednio umieszczać wyrażenia isSelected && 'card--selected' w template stringu, ponieważ przy fałszu do wyniku może trafić tekst false. Bezpieczne rozwiązania to operator trójargumentowy z wartością undefined, tablica filtrowana przez filter(Boolean) albo biblioteka clsx, która pomija wartości fałszywe.

Klasy CSS Modules należy pobierać z obiektu zaimportowanego jako styles, na przykład styles.card lub styles.selected. Przy kilku klasach można użyć clsx i składni obiektowej z kluczami zapisanymi w nawiasach kwadratowych, jak [styles.selected]: isSelected. Trzeba też sprawdzić, czy każda używana klasa faktycznie istnieje w pliku CSS.

Nie. Klasa odpowiada za wygląd, ale nie przekazuje prawdziwego stanu komponentu technologiom asystującym. Dla przycisku ładowania należy ustawić także disabled, a dla aktywnej zakładki użyć aria-selected zgodnie ze stanem wynikającym z propsów lub danych komponentu.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

react
dostępność
clsx
css modules
tailwind css
Autor Bruno Krawczyk
Bruno Krawczyk
Mam na imię Bruno i od 8 lat zgłębiam tajniki programowania w ekosystemie .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z technologią zaczęła się od ciekawości, jak złożone systemy mogą ułatwiać codzienne życie i rozwiązywać realne problemy. Dziś moją misją jest dzielenie się tą wiedzą, starając się przybliżyć nawet najbardziej skomplikowane zagadnienia w sposób zrozumiały i przystępny dla każdego. W moich artykułach na kursdotnet.pl skupiam się na praktycznych aspektach, analizuję najnowsze trendy i weryfikuję informacje, aby dostarczyć Wam treści, które są nie tylko dokładne i aktualne, ale przede wszystkim użyteczne w Waszej własnej ścieżce rozwoju technologicznego.

Udostępnij artykuł

Napisz komentarz