Dotychczas skupialiśmy się na logice aplikacji – komponenty, stan (state), efekty, nawigację (routing). Ale równie ważny jest wygląd aplikacji. Użytkownicy oceniają aplikację w pierwszej kolejności po wyglądzie, a dopiero potem po funkcjonalności. Dobry projekt wizualny (design) to nie luksus – to konieczność.
W świecie React mamy wiele sposobów na stylowanie komponentów. Od klasycznego CSS, przez CSS Modules, preprocesory jak SASS czy LESS, CSS-in-JS (np. Styled Components, Emotion), aż po frameworki oparte na klasach użytkowych (utility-first) jak Tailwind CSS. Każde podejście ma swoje zalety i wady.
W tym wpisie poznamy najpopularniejsze metody stylowania w React, nauczymy się kiedy która jest najlepsza, zbudujemy kompletne, responsywne komponenty, a także poznamy najlepsze praktyki (best practices) dla skalowalnego CSS. To będzie wizualny wpis – przygotuj się na dużo kodu CSS i pięknych przykładów!
Opcje stylowania w React
1. Style wbudowane (Inline Styles)
Najprostszy sposób – style bezpośrednio w TSX jako obiekt JavaScript:
Znajomy, standardowy CSS - łatwy start dla każdego
Pełna moc CSS (pseudo-selektory, zapytania o media)
Szybki dla osób znających CSS
Wady:
Globalny namespace – klasa .button w jednym komponencie może kolidować z .button w drugim
Trudne zarządzanie w dużych projektach - ciężko śledzić, które style gdzie są używane
Brak bezpieczeństwa typów (type safety) - literówka w nazwie klasy nie zostanie wykryta
Trudne usuwanie nieużywanego kodu (Dead code elimination)
💡 Kiedy używać: Małe projekty, proste strony, gdy zespół dobrze zna CSS i projekt nie będzie się mocno rozrastał.
3. CSS Modules
CSS z lokalnym zasięgiem (scope) - to ulepszony zwykły CSS, gdzie każda klasa jest automatycznie unikalna dla danego komponentu. Eliminuje to problem kolizji nazw klas!
// Button.tsx
import styles from './Button.module.css'; // Import jako obiekt
function Button() {
// styles.button to tak naprawdę coś jak "Button_button__x7s9K"
return <button className={styles.button}>Kliknij mnie</button>;
}
✨ Jak to działa? Narzędzia budujące (Vite/Webpack) automatycznie zamieniają .button na unikalną nazwę klasy jak .Button_button__x7s9K. Dzięki temu każdy komponent ma swoje unikalne klasy i nie ma kolizji!
Zalety:
Lokalny zasięg (local scope) – brak kolizji nazw między komponentami
Wsparcie dla TypeScript (z odpowiednią konfiguracją)
Kompozycja stylów dzięki słowu kluczowemu composes
Wady:
Wymaga narzędzia do budowania (build tool) jak Vite czy Webpack
Nadal możliwe style globalne przez :global()
Dynamiczne wartości wymagają połączenia z inline styles
💡 Kiedy używać: Średnie i duże projekty, gdy chcesz CSS ale z lokalnym zasięgiem. To najpopularniejszy wybór dla projektów React!
4. SASS/SCSS
Preprocesor CSS - to narzędzie, które rozszerza możliwości CSS o dodatkowe funkcje jak zmienne, zagnieżdżanie, funkcje. Kod SASS/SCSS jest później kompilowany do zwykłego CSS.
Funkcje i operacje matematyczne (np. lighten($color, 10%))
Lepsze zarządzanie w dużych projektach dzięki modułom
Może używać CSS Modules (.module.scss)
Wady:
Dodatkowy preprocesor do nauki - musisz poznać składnię SASS
Krok kompilacji (build step) wymagany
Może prowadzić do nadmiernego zagnieżdżania (over-nesting) - trudniejszy w czytaniu kod
💡 Kiedy używać: Duże projekty z skomplikowanym projektem wizualnym, gdy zespół zna SASS lub chce używać jego zaawansowanych funkcji.
5. Styled Components (CSS-in-JS)
Podejście CSS-in-JS - piszesz style bezpośrednio w plikach JavaScript/TypeScript jako szablon stringów. Style są przypisane do komponentów, a biblioteka generuje unikalne klasy CSS w runtime.
Automatyczne usuwanie nieużywanego kodu (Dead code elimination)
Wbudowane wsparcie dla motywów (Theming)
Wady:
Narzut wykonawczy (runtime overhead) - style są generowane podczas działania aplikacji
Większy rozmiar paczki (bundle size)
Krzywa uczenia się (learning curve) - nowa składnia do opanowania
Trudniejsze debugowanie (debugging) przez generowane nazwy klas
💡 Kiedy używać: Duże, dynamiczne aplikacje z dużą ilością interaktywnego interfejsu użytkownika, gdy potrzebujesz często zmieniać style na podstawie stanu aplikacji.
6. Tailwind CSS
Framework CSS oparty na klasach użytkowych (Utility-first) - zamiast pisać własne style, łączysz gotowe, małe klasy CSS (np. bg-blue-500 dla niebieskiego tła, px-4 dla paddingu).
Responsywność łatwa do osiągnięcia (np. md:flex lg:grid)
Wsparcie dla TypeScript przez wtyczki IDE
Nie musisz wymyślać nazw klas - używasz gotowych
Wady:
HTML "brudny" od wielu klas - czasem 10-15 klas na jeden element
Krzywa uczenia się (learning curve) - musisz zapamiętać nazwy klas
Ciężko dostosować projekt wizualny poza gotowy system Tailwind
Może być powtarzalny - te same zestawy klas w wielu miejscach
💡 Kiedy używać: Szybkie prototypowanie, gdy chcesz spójny system projektowania, duże projekty gdzie wiele osób pracuje nad UI i potrzebują spójnych stylów.
CSS Modules – szczegółowo
CSS Modules to najpopularniejszy wybór dla projektów React z TypeScript. To zwykły CSS, ale z automatycznym lokalnym zasięgiem - każda klasa jest unikalna dla komponentu. Zobaczmy jak używać go profesjonalnie.
Styled Components to popularna biblioteka CSS-in-JS, która pozwala pisać style bezpośrednio w komponencie. Świetnie sprawdza się w dużych aplikacjach z dynamicznymi stylami.
Podstawy
import styled from 'styled-components';
// Tworzysz stylowany button - to komponent React ze stylami
const Button = styled.button`
background-color: #3498db;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
/* & oznacza sam siebie (jak w SASS) */
&:hover {
background-color: #2980b9;
}
&:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
`;
// Użycie - to normalny komponent React!
<Button>Kliknij mnie</Button>
<Button disabled>Wyłączony</Button>
Dynamiczne style na podstawie props
To największa siła Styled Components - możesz zmieniać style na podstawie właściwości (props) przekazanych do komponentu!
// Button.tsx - dostęp do motywu przez props.theme
const Button = styled.button`
background-color: ${props => props.theme.colors.primary}; // Kolor z motywu
color: white;
padding: ${props => props.theme.spacing.medium}; // Padding z motywu
`;
Style globalne (Global Styles)
Czasem potrzebujesz stylów globalnych (reset CSS, style dla body itp.). Styled Components ma do tego specjalny komponent:
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
/* Reset domyślnych marginesów i paddingów */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Style dla całej strony */
body {
font-family: 'Inter', sans-serif;
background-color: ${props => props.theme.colors.background};
color: ${props => props.theme.colors.text};
}
/* Style dla wszystkich linków */
a {
text-decoration: none;
color: ${props => props.theme.colors.primary};
}
`;
function App() {
return (
<ThemeProvider theme={lightTheme}>
<GlobalStyle /> {/* Dodaj GlobalStyle jako komponent */}
<AppContent />
</ThemeProvider>
);
}
Tailwind CSS – szczegółowo
Tailwind CSS to framework oparty na małych, pojedynczych klasach użytkowych (utility classes). Zamiast pisać własny CSS, łączysz gotowe klasy jak klocki. Na przykład: bg-blue-500 (niebieskie tło), p-4 (padding), rounded (zaokrąglone rogi).
Responsywność to dostosowanie wyglądu do różnych rozmiarów ekranów (telefon, tablet, komputer). Tailwind ma wbudowane punkty przerwania (breakpointy) - prefiksy określające od jakiej szerokości ekranu mają działać style.
Responsywność (Responsive Web Design) to technika tworzenia stron dostosowujących się do różnych rozmiarów ekranów - od telefonu po duży monitor. To absolutna podstawa współczesnego web developmentu!
Podejście Mobile-First
Mobile-first oznacza, że najpierw piszesz style dla telefonów (najmniejsze ekrany), a potem dodajesz style dla większych ekranów. To lepsze niż odwrotnie!
/* Mobile first - zaczynamy od telefonu */
.container {
display: grid;
grid-template-columns: 1fr; /* 1 kolumna na telefonie */
gap: 1rem;
}
/* Tablet - od 768px w górę */
@media (min-width: 768px) {
.container {
grid-template-columns: repeat(2, 1fr); /* 2 kolumny */
}
}
/* Desktop - od 1024px w górę */
@media (min-width: 1024px) {
.container {
grid-template-columns: repeat(3, 1fr); /* 3 kolumny */
}
}
// Tailwind - automatyczna responsywność w jednej linii!
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Automatyczna responsywność */}
</div>
Container Queries (nowość!)
Container Queries to nowa technika CSS, gdzie style zmieniają się w zależności od rozmiaru rodzica (kontenera), a nie całego okna przeglądarki. Super przydatne dla komponentów!
.card-container {
container-type: inline-size; /* Uczyń ten element kontenerem */
}
.card {
display: flex;
flex-direction: column; /* Domyślnie w kolumnie */
}
/* Gdy kontener ma min 400px szerokości */
@container (min-width: 400px) {
.card {
flex-direction: row; /* Zmień na wiersz */
}
}
Praktyczny przykład: Kompletny komponent Card
Połączmy całą zdobytą wiedzę w prawdziwy, produkcyjny komponent karty (Card). Zobacz jak ten sam komponent wygląda w różnych podejściach do stylowania!
Wersja CSS Modules
// Card.tsx
import styles from './Card.module.css';
import classNames from 'classnames';
interface CardProps {
title: string;
description: string;
image: string;
tags: string[];
onCardClick?: () => void;
featured?: boolean; // Czy karta jest wyróżniona
}
function Card({ title, description, image, tags, onCardClick, featured }: CardProps) {
return (
<article
className={classNames(
styles.card,
{ [styles.featured]: featured } // Dodaj klasę featured tylko jeśli featured=true
)}
onClick={onCardClick}
>
{/* Kontener obrazka */}
<div className={styles.imageWrapper}>
<img src={image} alt={title} className={styles.image} />
{featured && <span className={styles.badge}>Featured</span>}
</div>
{/* Treść karty */}
<div className={styles.content}>
<h3 className={styles.title}>{title}</h3>
<p className={styles.description}>{description}</p>
{/* Tagi */}
<div className={styles.tags}>
{tags.map(tag => (
<span key={tag} className={styles.tag}>{tag}</span>
))}
</div>
</div>
</article>
);
}
export default Card;
Wybierz JEDNO podejście do stylowania dla całego projektu i trzymaj się go. Mieszanie różnych metod prowadzi do chaosu!
✅ Dobrze - cały projekt w jednym stylu:
- Cały projekt: CSS Modules
- Cały projekt: Tailwind
- Cały projekt: Styled Components
❌ Źle - mieszanka wszystkich:
- Button.tsx → Styled Components
- Card.tsx → Tailwind
- Modal.tsx → CSS Modules
(To prowadzi do bałaganu!)
2. Tokeny projektowe (Design Tokens)
Design Tokens to zmienne definiujące kolory, odstępy, czcionki itp. Definiujesz je raz, używasz wszędzie. Gdy chcesz zmienić kolor główny, zmieniasz w jednym miejscu!
✨ Zaleta tokenów: Gdy chcesz zmienić kolor główny w całej aplikacji, zmieniasz tylko --color-primary w jednym miejscu, a zmiana propaguje się wszędzie!
3. Konwencja nazewnictwa BEM (dla CSS/CSS Modules)
BEM (Block Element Modifier) to metodyka nazewnictwa klas CSS, która pomaga utrzymać porządek. Block = komponent, Element = część komponentu, Modifier = wariacja.
/* Block - główny komponent */
.card { }
/* Element - część komponentu (używamy __) */
.card__title { }
.card__description { }
.card__image { }
/* Modifier - modyfikacja wyglądu (używamy --) */
.card--featured { } /* Wyróżniona karta */
.card--large { } /* Większa karta */
.card__title--bold { } /* Pogrubiony tytuł */
✨ Przykład BEM w praktyce: .card = cała karta .card__title = tytuł w karcie .card--featured = wyróżniona wersja karty
4. Unikaj głębokiego zagnieżdżania
Zbyt głębokie zagnieżdżanie selektorów CSS utrudnia czytanie i może prowadzić do problemów ze specyficznością (specificity).
/* ❌ Źle - zbyt głęboko zagnieżdżone */
.card .content .header .title .text {
color: red;
}
/* ✅ Dobrze - płaska struktura */
.card { }
.card-title { }
.card-text { }
/* Lub z BEM */
.card__title { }
.card__text { }
5. Zapytania o media Mobile-First
Zawsze projektuj najpierw dla urządzeń mobilnych (najmniejsze ekrany), potem dodawaj style dla większych. To łatwiejsze niż na odwrót!
/* ✅ Dobrze - Mobile first */
.container {
padding: 16px; /* Domyślnie dla telefonu */
}
/* Tablet i większe */
@media (min-width: 768px) {
.container {
padding: 32px; /* Więcej paddingu na większych ekranach */
}
}
/* ❌ Źle - Desktop first */
.container {
padding: 32px; /* Domyślnie dla dużych ekranów */
}
@media (max-width: 767px) {
.container {
padding: 16px; /* Zmniejsz dla małych - trudniej utrzymać */
}
}
6. Używaj zmiennych CSS dla wartości dynamicznych
Zmienne CSS (CSS Custom Properties) świetnie sprawdzają się do wartości zmienianych przez JavaScript, np. pasków postępu:
function ProgressBar({ progress }: { progress: number }) {
return (
<div
className={styles.progressBar}
style={{ '--progress': `${progress}%` } as React.CSSProperties} >
<div className={styles.fill} />
</div>
);
}
Dostępność (a11y) to projektowanie stron dostępnych dla wszystkich, w tym osób z niepełnosprawnościami. To nie opcja - to obowiązek!
// ✅ Dobre praktyki dostępności
<button
className={styles.button}
aria-label="Zamknij modal" // Opis dla czytników ekranu
aria-pressed={isActive} // Stan przycisku
>
<CloseIcon aria-hidden="true" /> {/* Ikona ukryta dla czytników */}
</button>
<nav aria-label="Główna nawigacja"> {/* Opisz rolę elementu */}
<ul className={styles.menu}>
<li><a href="/">Home</a></li>
</ul>
</nav>
/* Fokus dla nawigacji klawiaturą - BARDZO WAŻNE! */
.button:focus-visible {
outline: 2px solid #3498db;
outline-offset: 2px;
}
/* ❌ NIGDY nie usuwaj outline bez zamiennika! */
/* To uniemożliwia nawigację klawiaturą */
.button:focus {
outline: none; /* BARDZO ZŁA PRAKTYKA */
}
⚠️ Uwaga: Usunięcie outline bez dodania alternatywnego wskaźnika focusu uniemożliwia osobom używającym klawiatury (np. osobom niewidomym) nawigację po stronie. To naruszenie standardów dostępności!
8. Optymalizacja wydajności (Performance)
Wydajność (performance) wpływa na szybkość ładowania i działania strony. Wolne strony = frustracja użytkowników = utrata ruchu!
// ✅ Leniwe ładowanie obrazków (lazy loading)
// Obrazki ładują się dopiero gdy użytkownik je przewinie
<img
src={image}
alt={title}
loading="lazy" // Przeglądarka załaduje obrazek dopiero gdy będzie widoczny
className={styles.image}
/>
// ✅ Krytyczny CSS inline dla First Paint
// Style niezbędne do pierwszego wyrenderowania strony
<style dangerouslySetInnerHTML={{
__html: `.hero { height: 100vh; background: #3498db; }`
}} />
// Reszta stylów ładuje się osobno
💡 Wskazówka wydajnościowa: Używaj loading="lazy" dla wszystkich obrazków poza tymi widocznymi od razu (above the fold). To może zaoszczędzić megabajty danych!
Porównanie podejść
Zestawienie wszystkich metod - która najlepsza dla Twojego projektu?
Koszt wykonawczy (Runtime Cost - obciążenie podczas działania)
✅ Brak (CSS generowany wcześniej)
❌ Jest (style generowane w runtime)
✅ Brak (tylko klasy CSS)
Dynamiczne style (zmiana stylów na podstawie danych)
❌ Trudne (potrzebne inline styles)
✅ Łatwe (props → style)
⚠️ Średnie (warunkowe klasy)
Doświadczenie programisty (Developer Experience)
✅ Dobry (znajome narzędzia)
✅ Świetny (wszystko w jednym miejscu)
✅ Świetny (szybki development)
Ekosystem (biblioteki, narzędzia, społeczność)
✅ Duży
✅ Duży
✅ Ogromny
Refaktoryzacja (łatwość zmiany kodu)
⚠️ Średnie (trzeba śledzić pliki CSS)
✅ Łatwe (style razem z komponentem)
❌ Trudne (klasy powtarzane wszędzie)
Moja rekomendacja
Wybór podejścia zależy od typu i wielkości projektu. Oto moje sugestie:
🚀 Małe projekty / Prototypy / MVP:
→ Tailwind CSS Dlaczego? Najszybszy rozwój, nie musisz pisać CSS, od razu widzisz efekty. Idealny do szybkiego testowania pomysłów.
🏢 Średnie projekty / Startupy / Zespoły 2-5 osób:
→ CSS Modules Dlaczego? Balans między prostotą a mocą. Znasz CSS? Działasz od razu. Lokalny scope eliminuje konflikty. Najczęściej używane w React!
🏭 Duże projekty / Enterprise / Zespoły 10+ osób:
→ Styled Components lub CSS Modules + SASS Dlaczego? Skalowalne, dobrze zorganizowane, pełna kontrola. Styled Components dają dynamiczne style, SASS daje zaawansowane funkcje CSS.
🎨 Design Systems / Biblioteki komponentów:
→ Styled Components Dlaczego? Komponenty z props, wbudowane wsparcie dla motywów (theming), łatwe tworzenie wariantów. Idealne do bibliotek typu "Material-UI".
📐 Projekty z silnym systemem projektowym:
→ Tailwind CSS Dlaczego? Spójność od samego początku (consistency out of the box). Wszyscy w zespole używają tych samych klas = jednolity wygląd.
Podsumowanie
To był wizualny i praktyczny wpis! Nauczyliśmy się:
✅ Responsywność – podejście mobile-first, zapytania o media (media queries), punkty przerwania (breakpointy)
✅ Praktyczne przykłady – kompletny komponent Card w różnych podejściach
✅ Najlepsze praktyki (Best Practices) – 8 zasad profesjonalnego CSS
✅ Porównanie – kiedy które podejście wybrać
Stylowanie w React to osobny świat – teraz masz kompletną wiedzę by wybrać najlepsze narzędzie dla Twojego projektu i tworzyć piękne, responsywne interfejsy użytkownika!
W kolejnym wpisie poznamy optymalizację wydajności React – React.memo, useMemo, useCallback, leniwe ładowanie (lazy loading), dzielenie kodu (code splitting). Nauczymy się jak sprawić by aplikacja działała błyskawicznie!