Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-02-03
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-02-03
Udostępnij Udostępnij Kontakt
Wprowadzenie

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:

function Button() {
  return (
    <button style={{ 
      backgroundColor: '#3498db',  // Kolor tła przycisku
      color: 'white',              // Kolor tekstu
      padding: '10px 20px',        // Odstępy wewnętrzne
      border: 'none',              // Bez ramki
      borderRadius: '4px',         // Zaokrąglone rogi
      cursor: 'pointer'            // Kursor "ręka" po najechaniu
    }}>
      Kliknij mnie
    </button>
  );
}

Zalety:

  • Proste i szybkie w implementacji
  • Dynamiczne wartości (np. z props) łatwe do dodania
  • Style zamknięte w komponencie (scoped) - nie wyciekają na zewnątrz

Wady:

  • Brak pseudo-selektorów (:hover, :active, :focus)
  • Brak zapytań o media (media queries) dla responsywności
  • Duża powtarzalność kodu - te same style w wielu miejscach
  • Gorsza wydajność (performance) - inline styles są wolniejsze od CSS
  • Brak podpowiedzi IDE (autocomplete) dla właściwości CSS
💡 Kiedy używać: Tylko dla bardzo prostych, jednorazowych stylów lub gdy potrzebujesz dynamicznie zmieniać wartości na podstawie props/state.

2. Zewnętrzny plik CSS

Tradycyjne pliki CSS importowane do komponentu:

// Button.tsx
import './Button.css';  // Import pliku ze stylami

function Button() {
  return <button className="button">Kliknij mnie</button>;
}
/* Button.css */
.button {
  background-color: #3498db;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

/* Pseudo-klasa dla efektu po najechaniu myszką */
.button:hover {
  background-color: #2980b9;
}

Zalety:

  • 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>;
}
/* Button.module.css */
.button {
  background-color: #3498db;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.button:hover {
  background-color: #2980b9;
}
✨ 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
  • Standardowy CSS - piszesz normalnie, narzędzia robią resztę
  • 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.

npm install sass
// Button.tsx
import './Button.scss';

function Button() {
  return <button className="button">Kliknij mnie</button>;
}
/* Button.scss */
// Zmienne - definiujesz raz, używasz wszędzie
$primary-color: #3498db;
$primary-hover: #2980b9;

.button {
  background-color: $primary-color;  // Użycie zmiennej
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  // Zagnieżdżanie - & oznacza rodzica (.button)
  &:hover {
    background-color: $primary-hover;
  }
  
  // Modyfikator - tworzy klasę .button.large
  &.large {
    padding: 15px 30px;
    font-size: 18px;
  }
}

Zalety:

  • Zmienne, domieszki (mixins), zagnieżdżanie (nesting)
  • 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.

npm install styled-components
npm install --save-dev @types/styled-components
import styled from 'styled-components';

// Tworzysz stylowany komponent - to zwykły komponent React ze stylami
const Button = styled.button`
  background-color: #3498db;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    background-color: #2980b9;
  }
`;

function App() {
  // Używasz jak normalnego komponentu
  return <Button>Kliknij mnie</Button>;
}

Zalety:

  • Style zamknięte w komponencie (component-scoped)
  • Dynamiczne wartości z właściwości (props) - łatwo zmieniać style na podstawie danych
  • Pełne wsparcie dla TypeScript
  • Automatyczne dodawanie prefiksów przeglądarek (vendor prefixing)
  • 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).

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
function Button() {
  return (
    <button className="bg-blue-500 text-white px-5 py-2 rounded hover:bg-blue-600 cursor-pointer">
      Kliknij mnie
    </button>
  );
  {/* 
    bg-blue-500 = tło niebieskie
    text-white = biały tekst
    px-5 = padding poziomy
    py-2 = padding pionowy
    rounded = zaokrąglone rogi
    hover:bg-blue-600 = ciemniejszy niebieski po najechaniu
  */}
}

Zalety:

  • Bardzo szybki rozwój (development) - nie musisz pisać CSS
  • Spójność projektu (Consistency) - wbudowany system projektowania w klasach
  • Automatyczne usuwanie nieużywanych stylów (Purge CSS) – mały rozmiar końcowego pliku
  • 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.

Podstawowe użycie

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

interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';  // Wariant wyglądu
  size?: 'small' | 'medium' | 'large'; // Rozmiar
}

function Button({ children, variant = 'primary', size = 'medium' }: ButtonProps) {
  return (
    // Łączymy klasy - bazową + wariant + rozmiar
    <button className={`${styles.button} ${styles[variant]} ${styles[size]}`}>
      {children}
    </button>
  );
}

export default Button;
/* Button.module.css */
/* Bazowe style wspólne dla wszystkich przycisków */
.button {
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 500;
  transition: all 0.2s;  /* Płynne przejścia */
}

/* Warianty kolorystyczne */
.primary {
  background-color: #3498db;
  color: white;
}

.primary:hover {
  background-color: #2980b9;
}

.secondary {
  background-color: #95a5a6;
  color: white;
}

.secondary:hover {
  background-color: #7f8c8d;
}

/* Rozmiary */
.small {
  padding: 6px 12px;
  font-size: 14px;
}

.medium {
  padding: 10px 20px;
  font-size: 16px;
}

.large {
  padding: 14px 28px;
  font-size: 18px;
}

Kompozycja stylów (Composition)

CSS Modules wspierają kompozycję - możesz dziedziczyć style z innych klas używając słowa kluczowego composes. To eliminuje powtarzanie kodu!

/* Button.module.css */
/* Klasa bazowa z wspólnymi stylami */
.base {
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 500;
}

/* Klasa primary dziedziczy wszystko z .base */
.primary {
  composes: base;  /* Magiczne słowo - "weź wszystko z .base" */
  background-color: #3498db;
  color: white;
}

.secondary {
  composes: base;
  background-color: #95a5a6;
  color: white;
}

Biblioteka classnames

Dla łatwiejszego łączenia klas warunkowych. Zamiast długiego className={...} z ifami, używasz eleganckiej biblioteki:

npm install classnames
import styles from './Button.module.css';
import classNames from 'classnames';

function Button({ children, variant, size, disabled }: ButtonProps) {
  return (
    <button 
      className={classNames(
        styles.button,              // Zawsze dodaj
        styles[variant],            // Dodaj klasę wariantu
        styles[size],               // Dodaj klasę rozmiaru
        { [styles.disabled]: disabled }  // Dodaj tylko jeśli disabled=true
      )}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

TypeScript dla CSS Modules

Żeby TypeScript rozumiał pliki .module.css, dodaj definicje typów:

// global.d.ts (stwórz ten plik w katalogu src)
declare module '*.module.css' {
  const classes: { [key: string]: string };
  export default classes;
}

declare module '*.module.scss' {
  const classes: { [key: string]: string };
  export default classes;
}

Dla pełnego wsparcia TypeScript (podpowiedzi IDE dla nazw klas) możesz użyć wtyczki:

npm install --save-dev typescript-plugin-css-modules

Styled Components – szczegółowo

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!

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
}

const Button = styled.button<ButtonProps>`
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
  
  /* Warianty kolorystyczne - używamy props do wyboru koloru */
  background-color: ${props => 
    props.variant === 'primary' ? '#3498db' : '#95a5a6'
  };
  color: white;
  
  &:hover {
    background-color: ${props => 
      props.variant === 'primary' ? '#2980b9' : '#7f8c8d'
    };
  }
  
  /* Rozmiary - props.size określa padding */
  padding: ${props => {
    switch(props.size) {
      case 'small': return '6px 12px';
      case 'large': return '14px 28px';
      default: return '10px 20px';  // medium
    }
  }};
  
  /* Rozmiar czcionki również z props */
  font-size: ${props => {
    switch(props.size) {
      case 'small': return '14px';
      case 'large': return '18px';
      default: return '16px';
    }
  }};
`;

// Użycie z różnymi kombinacjami props
<Button variant="primary" size="large">Duży przycisk</Button>
<Button variant="secondary" size="small">Mały przycisk</Button>

Rozszerzanie stylów (Extending styles)

Możesz tworzyć nowe komponenty bazując na istniejących - dziedziczą wszystkie style i możesz dodać nowe:

// Bazowy przycisk ze wspólnymi stylami
const Button = styled.button`
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

// PrimaryButton dziedziczy wszystko z Button i dodaje swoje style
const PrimaryButton = styled(Button)`
  background-color: #3498db;
  color: white;
  
  &:hover {
    background-color: #2980b9;
  }
`;

// DangerButton też dziedziczy z Button
const DangerButton = styled(Button)`
  background-color: #e74c3c;
  color: white;
  
  &:hover {
    background-color: #c0392b;
  }
`;

Motywy (Theming)

Theming to system motywów (np. jasny/ciemny tryb), gdzie definiujesz kolory i inne wartości raz, a potem używasz ich w całej aplikacji.

// theme.ts - definicje motywów
export const lightTheme = {
  colors: {
    primary: '#3498db',
    secondary: '#95a5a6',
    background: '#ffffff',  // Białe tło
    text: '#2c3e50'         // Ciemny tekst
  },
  spacing: {
    small: '8px',
    medium: '16px',
    large: '24px'
  }
};

export const darkTheme = {
  colors: {
    primary: '#3498db',
    secondary: '#95a5a6',
    background: '#2c3e50',  // Ciemne tło
    text: '#ecf0f1'         // Jasny tekst
  },
  spacing: {
    small: '8px',
    medium: '16px',
    large: '24px'
  }
};
// App.tsx - użycie motywów
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    // ThemeProvider udostępnia motyw wszystkim komponentom
    <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
      <Container>
        <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
          Przełącz motyw
        </button>
      </Container>
    </ThemeProvider>
  );
}
// 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).

Instalacja i konfiguracja

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js - konfiguracja Tailwind
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",  // Gdzie Tailwind ma szukać klas
  ],
  theme: {
    extend: {
      colors: {
        primary: '#3498db',      // Twoje własne kolory
        secondary: '#95a5a6',
      }
    },
  },
  plugins: [],
}
/* index.css - import dyrektyw Tailwind */
@tailwind base;        /* Style resetujące */
@tailwind components;  /* Style komponentów */
@tailwind utilities;   /* Klasy użytkowe */

Podstawowe użycie

function Button({ children, variant = 'primary' }: ButtonProps) {
  // Wspólne style dla wszystkich przycisków
  const baseClasses = "px-5 py-2 rounded font-medium transition-colors";
  
  // Style specyficzne dla wariantów
  const variantClasses = {
    primary: "bg-blue-500 text-white hover:bg-blue-600",
    secondary: "bg-gray-500 text-white hover:bg-gray-600",
    outline: "border-2 border-blue-500 text-blue-500 hover:bg-blue-50"
  };
  
  return (
    <button className={`${baseClasses} ${variantClasses[variant]}`}>
      {children}
    </button>
  );
}

Responsywność (Responsive Design)

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.

<div className="
  grid 
  grid-cols-1          /* Telefon: 1 kolumna */
  sm:grid-cols-2       /* Małe ekrany (≥640px): 2 kolumny */
  md:grid-cols-3       /* Średnie (≥768px): 3 kolumny */
  lg:grid-cols-4       /* Duże (≥1024px): 4 kolumny */
  xl:grid-cols-6       /* Bardzo duże (≥1280px): 6 kolumn */
  gap-4                /* Odstęp między elementami */
">
  {/* Karty produktów */}
</div>

📱 Punkty przerwania (breakpointy) w Tailwind:

  • sm: – od 640px (małe tablety)
  • md: – od 768px (tablety)
  • lg: – od 1024px (laptopy)
  • xl: – od 1280px (desktopy)
  • 2xl: – od 1536px (duże monitory)

Tworzenie własnych komponentów z Tailwind

Możesz tworzyć komponenty React z Tailwind i dodatkowo przekazywać własne klasy przez props:

// components/Card.tsx
interface CardProps {
  children: React.ReactNode;
  className?: string;  // Opcjonalne dodatkowe klasy
}

function Card({ children, className = '' }: CardProps) {
  return (
    <div className={`
      bg-white 
      rounded-lg 
      shadow-md              /* Cień */
      p-6                    /* Padding */
      hover:shadow-lg        /* Większy cień po najechaniu */
      transition-shadow      /* Płynna zmiana cienia */
      ${className}           /* Dodatkowe klasy z props */
    `}>
      {children}
    </div>
  );
}

// Użycie z dodatkowymi klasami
<Card className="max-w-md mx-auto">
  <h2 className="text-2xl font-bold mb-4">Tytuł</h2>
  <p className="text-gray-600">Treść karty...</p>
</Card>

Dyrektywa @apply

@apply to dyrektywa Tailwind pozwalająca grupować często używane kombinacje klas w jeden styl CSS. Zmniejsza to powtarzalność.

/* styles.css */
@layer components {
  /* Zamiast pisać te same klasy 100x, definiujesz raz */
  .btn-primary {
    @apply px-5 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors;
  }
  
  .card {
    @apply bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow;
  }
}
/* Teraz używasz zwięźle */
<button className="btn-primary">Click me</button>
<div className="card">Card content</div>

Biblioteka clsx z Tailwind

clsx to pomocnicza biblioteka do warunkowego łączenia klas CSS. Świetnie współgra z Tailwind!

npm install clsx
import clsx from 'clsx';

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  children: React.ReactNode;
}

function Button({ variant = 'primary', size = 'md', disabled, children }: ButtonProps) {
  return (
    <button
      className={clsx(
        // Style bazowe - zawsze obecne
        'rounded font-medium transition-colors',
        
        // Rozmiary - dodaj odpowiednią klasę w zależności od size
        {
          'px-3 py-1 text-sm': size === 'sm',
          'px-5 py-2 text-base': size === 'md',
          'px-7 py-3 text-lg': size === 'lg',
        },
        
        // Warianty kolorystyczne
        {
          'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
          'bg-gray-500 text-white hover:bg-gray-600': variant === 'secondary',
          'bg-red-500 text-white hover:bg-red-600': variant === 'danger',
        },
        
        // Stany - dodaj tylko jeśli disabled=true
        {
          'opacity-50 cursor-not-allowed': disabled,
        }
      )}
      disabled={disabled}
    >
      {children}
    </button>
  );
}
Responsywność

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!

// CSS Modules
<div className={styles.container}>
  {/* Mobile: 1 kolumna, Tablet: 2 kolumny, Desktop: 3 kolumny */}
</div>
/* 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;
/* Card.module.css */
/* Bazowe style karty */
.card {
  background: white;
  border-radius: 12px;
  overflow: hidden;  /* Zaokrąglone rogi dla obrazka */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;  /* Płynne animacje */
  cursor: pointer;
  display: flex;
  flex-direction: column;
}

/* Efekt po najechaniu myszką */
.card:hover {
  transform: translateY(-4px);  /* Przesuń w górę */
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);  /* Większy cień */
}

/* Karta wyróżniona - niebieska ramka */
.card.featured {
  border: 2px solid #3498db;
}

/* Kontener obrazka - proporcje 16:9 */
.imageWrapper {
  position: relative;
  width: 100%;
  padding-top: 56.25%;  /* 16:9 aspect ratio (9/16 * 100) */
  overflow: hidden;
}

/* Obrazek wypełnia cały kontener */
.image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;  /* Kadruj obrazek bez zniekształceń */
  transition: transform 0.3s ease;
}

/* Zoom obrazka po najechaniu na kartę */
.card:hover .image {
  transform: scale(1.05);  /* Powiększ o 5% */
}

/* Odznaka "Featured" */
.badge {
  position: absolute;
  top: 12px;
  right: 12px;
  background: #3498db;
  color: white;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
}

/* Treść karty */
.content {
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;  /* Zajmij pozostałą przestrzeń */
}

.title {
  font-size: 20px;
  font-weight: 700;
  color: #2c3e50;
  margin: 0;
  line-height: 1.3;
}

/* Opis z ograniczeniem do 3 linii */
.description {
  font-size: 14px;
  color: #7f8c8d;
  line-height: 1.6;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;  /* Maksymalnie 3 linie */
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Kontener tagów */
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: auto;  /* Przyklej tagi na dół karty */
}

/* Pojedynczy tag */
.tag {
  display: inline-block;
  padding: 4px 12px;
  background: #ecf0f1;
  color: #2c3e50;
  border-radius: 16px;
  font-size: 12px;
  font-weight: 500;
}

/* Responsywność - na tabletach karta poziomo */
@media (min-width: 768px) {
  .card {
    flex-direction: row;  /* Obrazek obok tekstu */
  }
  
  .imageWrapper {
    flex: 0 0 40%;  /* Obrazek zajmuje 40% szerokości */
    padding-top: 0;
    height: auto;
  }
  
  .image {
    position: static;
    height: 100%;
  }
}

Wersja Tailwind

Ten sam komponent, ale ze stylami Tailwind - wszystko w className:

import clsx from 'clsx';

interface CardProps {
  title: string;
  description: string;
  image: string;
  tags: string[];
  onCardClick?: () => void;
  featured?: boolean;
}

function Card({ title, description, image, tags, onCardClick, featured }: CardProps) {
  return (
    <article
      className={clsx(
        // Style bazowe
        'bg-white rounded-xl overflow-hidden shadow-md hover:shadow-xl',
        'transition-all duration-300 cursor-pointer',
        'hover:-translate-y-1',  // Przesuń w górę po hover
        'flex flex-col md:flex-row',  // Kolumna na mobile, wiersz na tablet+
        // Warunkowa ramka dla featured
        featured && 'border-2 border-blue-500'
      )}
      onClick={onCardClick}
    >
      {/* Kontener obrazka */}
      <div className="relative w-full md:w-2/5 pt-[56.25%] md:pt-0 overflow-hidden">
        <img
          src={image}
          alt={title}
          className="absolute top-0 left-0 md:static w-full h-full object-cover transition-transform duration-300 hover:scale-105"
        />
        {featured && (
          <span className="absolute top-3 right-3 bg-blue-500 text-white px-3 py-1 rounded-full text-xs font-semibold">
            Featured
          </span>
        )}
      </div>
      
      {/* Treść karty */}
      <div className="p-5 flex flex-col gap-3 flex-1">
        <h3 className="text-xl font-bold text-gray-800 leading-tight">
          {title}
        </h3>
        
        {/* Opis z ograniczeniem do 3 linii */}
        <p className="text-sm text-gray-600 line-clamp-3 leading-relaxed">
          {description}
        </p>
        
        {/* Tagi */}
        <div className="flex flex-wrap gap-2 mt-auto">
          {tags.map(tag => (
            <span
              key={tag}
              className="inline-block px-3 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium"
            >
              {tag}
            </span>
          ))}
        </div>
      </div>
    </article>
  );
}

export default Card;
Najlepsze praktyki (Best Practices)

1. Konsystencja w projekcie

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!

/* tokens.css - zmienne CSS (CSS Variables) */
:root {
  /* Kolory */
  --color-primary: #3498db;
  --color-secondary: #95a5a6;
  --color-success: #2ecc71;
  --color-danger: #e74c3c;
  --color-warning: #f39c12;
  
  /* Odstępy (spacing) */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  
  /* Rozmiary czcionek (typography) */
  --font-size-sm: 14px;
  --font-size-base: 16px;
  --font-size-lg: 18px;
  --font-size-xl: 24px;
  --font-size-2xl: 32px;
  
  /* Cienie (shadows) */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}

/* Użycie w stylach */
.button {
  background-color: var(--color-primary);
  padding: var(--spacing-md);
  font-size: var(--font-size-base);
  box-shadow: var(--shadow-md);
}
✨ 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>
    );
}
/* CSS */
.progressBar {
  width: 100%;
  height: 20px;
  background: #ecf0f1;
  border-radius: 10px;
  overflow: hidden;
}

.fill {
  height: 100%;
  width: var(--progress);  /* Wartość z inline style */
  background: #3498db;
  transition: width 0.3s ease;  /* Płynna animacja */
}

7. Dostępność (Accessibility) - nie zapomnij!

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?

Aspekt CSS Modules Styled Components Tailwind
Krzywa uczenia się
(jak trudno się nauczyć)
✅ Łatwy
(znasz CSS = działasz)
⚠️ Średni
(nowa składnia)
⚠️ Średni
(nazwy klas do zapamiętania)
Bezpieczeństwo typów
(Type Safety - wykrywanie błędów)
⚠️ Częściowy
(z wtyczkami)
✅ Pełny
(TypeScript support)
❌ Brak
(tylko nazwy klas)
Rozmiar paczki
(Bundle Size - wielkość plików)
✅ Mały
(~10-20KB)
⚠️ Średni
(~40-50KB biblioteka)
✅ Mały
(z purge CSS)
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ę:

  • 6 sposobów stylowaniastyle wbudowane (inline), CSS, CSS Modules, SASS, Styled Components, Tailwind
  • CSS Modules szczegółowokompozycja (composition), TypeScript, biblioteka classnames
  • Styled Components szczegółowoprops dynamiczne, motywy (theming), rozszerzanie stylów (extending), style globalne
  • Tailwind CSS szczegółowo – konfiguracja, responsywność, @apply, clsx
  • 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 ReactReact.memo, useMemo, useCallback, leniwe ładowanie (lazy loading), dzielenie kodu (code splitting). Nauczymy się jak sprawić by aplikacja działała błyskawicznie!

🎯 Zadanie dla Ciebie

Stwórz kompletny komponent ProductCard (karta produktu):

  1. Wybierz podejście (CSS Modules, Styled Components lub Tailwind)
  2. Zaimplementuj responsywność (telefon, tablet, komputer)
  3. Dodaj efekty po najechaniu (hover effects), płynne przejścia (transitions)
  4. Dodaj warianty (variants) - widok siatki (grid view) i widok listy (list view)
  5. (Bonus) Dodaj wsparcie dla trybu ciemnego (dark mode)