Storybook w React – jak skonfigurować i dokumentować komponenty

Mateusz Bogolubow

Mateusz Bogolubow

07-11-2026

Instalacja Storybooka w projekcie React z Vite, pierwsze stories, args i kontrolki, automatyczna dokumentacja oraz rozwiązania typowych problemów z TypeScriptem i Vite.

Storybook w React – jak skonfigurować i dokumentować komponenty
Storybook to narzędzie, które pozwala oglądać, testować ręcznie i dokumentować komponenty interfejsu w izolacji, bez uruchamiania całej aplikacji. W projekcie React instalujesz go jednym poleceniem, a każdy komponent opisujesz w pliku *.stories, w którym definiujesz jego warianty (stories) i propsy (args). Z tych opisów Storybook buduje przeglądarkową dokumentację komponentów.

Jeśli budujesz design system w projekcie frontendowym, prędzej czy później potrzebujesz miejsca, w którym każdy komponent można zobaczyć, wypróbować i opisać. Najpopularniejszym narzędziem do tego jest Storybook. Ten poradnik prowadzi od instalacji do pierwszej dokumentacji i omawia problemy, na które programiści trafiają najczęściej.

Czym jest Storybook i do czego służy w projekcie React?

Storybook to osobna aplikacja uruchamiana obok Twojego projektu. Pokazuje komponenty w izolacji: każdy w wybranym stanie, bez routingu, logowania i danych z API. Pojedynczy stan komponentu nazywa się tu historią (story), np. „przycisk główny", „przycisk wyłączony" albo „pole z błędem".

Dzięki temu możesz:

  • oglądać wszystkie warianty komponentu w jednym miejscu,
  • zmieniać propsy w interfejsie i od razu widzieć efekt,
  • pokazać komponenty designerowi bez uruchamiania aplikacji,
  • wygenerować dokumentację na podstawie kodu.

Jak zainstalować Storybooka w projekcie React z Vite?

W istniejącym projekcie React zbudowanym na Vite wystarczy jedno polecenie z katalogu głównego:

npm create storybook@latest

Instalator wykrywa framework i bundler, instaluje potrzebne pakiety, tworzy katalog .storybook oraz przykładowe stories. Uruchomisz je poleceniem, które instalator dopisze do package.json:

npm run storybook

Storybook otworzy się w przeglądarce, zwykle pod adresem http://localhost:6006. Najważniejszy plik konfiguracyjny to .storybook/main.ts:

import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  // gdzie Storybook szuka plików ze stories
  stories: ["../src/**/*.stories.@(ts|tsx)"],
  addons: ["@storybook/addon-docs"],
  framework: "@storybook/react-vite",
};

export default config;

Pole stories wskazuje, gdzie leżą pliki z opisami komponentów, a framework mówi, że projekt używa Reacta z Vite. Instalator doda zwykle jeszcze kilka addonów (np. do testów i dostępności), więc Twoja lista będzie dłuższa. Do dokumentacji z tego artykułu potrzebny jest @storybook/addon-docs.

Jak napisać pierwsze story?

Plik ze stories leży obok komponentu i ma nazwę w rodzaju Button.stories.tsx. Zakładam komponent Button z propem variant typu "primary" | "secondary":

import type { Meta, StoryObj } from "@storybook/react-vite";
import { Button } from "./Button";

const meta = {
  title: "Komponenty/Button",
  component: Button,
  tags: ["autodocs"],
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: { variant: "primary", children: "Zapisz" },
};

export const Secondary: Story = {
  args: { variant: "secondary", children: "Anuluj" },
};

meta opisuje cały komponent (tytuł w menu, sam komponent, tagi), a każdy wyeksportowany obiekt to jedna story. Dzięki satisfies Meta<typeof Button> TypeScript sprawdza, czy args pasują do propsów komponentu, i podpowiada je w edytorze.

Czym są args, argTypes, parameters i decorators?

Cztery pojęcia pojawiają się w niemal każdym pliku ze stories. Zanim do nich przejdziesz, jedna zasada, która oszczędzi Ci pracy: pisz story dla stanów, a nie dla każdej kombinacji propsów. Stany takie jak loading, error i empty pokazują, jak komponent faktycznie się zachowuje. Dziesięć dobrze dobranych stanów jest wartościowsze niż sto wygenerowanych kombinacji, bo przegląda je człowiek.

PojęcieDo czego służyPrzykład
argsWartości propsów w danej story{ variant: "primary" }
argTypesOpis propsa i rodzaj kontrolki (controls) w panelucontrol: "select"
parametersUstawienia wyświetlania, np. układ lub tłolayout: "centered"
decoratorsOpakowanie renderowania, np. kontekst lub motyw (decorator)<ThemeProvider>

Kontrolki (controls) to panel pod podglądem, w którym zmieniasz args bez edycji kodu. Storybook zgaduje ich rodzaj z typów TypeScript, a argTypes pozwala to doprecyzować, np. wymusić listę wyboru dla wariantu.

Jak wyglądają stories dla różnych komponentów?

Zasada „story dla stanów" najlepiej widać na komponentach, które mają coś więcej niż jeden wariant wizualny. Trzy przykłady, każdy z innym problemem.

Pole formularza: stany, o których łatwo zapomnieć

import { useId } from "react";

type InputProps = {
  label: string;
  error?: string;
} & React.InputHTMLAttributes<HTMLInputElement>;

export function Input({ label, error, id, ...props }: InputProps) {
  const generatedId = useId();
  const inputId = id ?? generatedId;

  return (
    <div>
      <label htmlFor={inputId}>{label}</label>
      <input
        id={inputId}
        aria-invalid={Boolean(error)}
        aria-describedby={error ? `${inputId}-error` : undefined}
        {...props}
      />
      {error && <p id={`${inputId}-error`} role="alert">{error}</p>}
    </div>
  );
}
const meta = {
  title: "Komponenty/Input",
  component: Input,
  tags: ["autodocs"],
} satisfies Meta<typeof Input>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: { label: "Adres e-mail", placeholder: "jan@example.com" },
};

export const WithError: Story = {
  args: { label: "Adres e-mail", error: "Podaj poprawny adres" },
};

export const Disabled: Story = {
  args: { label: "Adres e-mail", disabled: true },
};

Każda story to osobny stan, który designer może obejrzeć bez wypełniania formularza.

Etykieta (Badge): wszystkie warianty obok siebie

Gdy komponent ma kilka wariantów kolorystycznych, jedna story z render pokazuje je wszystkie naraz i od razu widać, czy się różnią. Jeśli propsy są wymagane, TypeScript zażąda ich także w story, które ich nie używa. Dlatego ustaw domyślne args raz, w meta:

const meta = {
  title: "Komponenty/Badge",
  component: Badge,
  tags: ["autodocs"],
  args: { tone: "neutral", children: "Szkic" },
} satisfies Meta<typeof Badge>;
export const AllTones: Story = {
  render: () => (
    <div style={{ display: "flex", gap: "0.5rem" }}>
      <Badge tone="neutral">Szkic</Badge>
      <Badge tone="success">Opublikowano</Badge>
      <Badge tone="danger">Błąd</Badge>
    </div>
  ),
};

Okno modalne (Modal): komponent ze stanem

Modal otwiera się po kliknięciu, więc sama story z open: true nie pokaże, jak się zachowuje. Użyj render, żeby dołożyć stan. Wymagane propsy open i onClose ustaw domyślnie w meta (args: { open: false, onClose: () => {} }), a w story podaj tylko to, co się zmienia:

export const Interactive: Story = {
  args: { title: "Usuń konto", children: "Tej operacji nie można cofnąć." },
  render: function Render(args) {
    const [open, setOpen] = useState(false);

    return (
      <>
        <button onClick={() => setOpen(true)}>Otwórz</button>
        <Modal {...args} open={open} onClose={() => setOpen(false)} />
      </>
    );
  },
};

Nazwana funkcja Render jest potrzebna, żeby reguły hooków w ESLint traktowały ją jak komponent.

Jak ukryć kontrolkę tylko dla jednej story?

To jedno z najczęściej wyszukiwanych pytań o Storybooka. Jeśli w jednej story prop nie ma sensu (np. variant w story, która pokazuje tylko stan wyłączony), nadpisz argTypes na poziomie tej story, a nie całego komponentu:

export const Disabled: Story = {
  args: { variant: "primary", children: "Zapisz", disabled: true },
  argTypes: {
    variant: { control: false },
  },
};

control: false usuwa kontrolkę tylko z tej story. Pozostałe story nadal ją mają, bo ich argTypes dziedziczą z meta. Gdy chcesz ukryć prop także w tabeli dokumentacji, użyj table: { disable: true } wewnątrz argTypes.

Jak wygenerować dokumentację komponentów automatycznie?

Tag autodocs w meta powoduje, że Storybook sam zbuduje stronę „Docs" dla komponentu: z opisem, przykładami wszystkich story i tabelą propsów. Treść tabeli bierze z typów i komentarzy w kodzie, więc im lepiej opiszesz propsy, tym lepsza dokumentacja:

type ButtonProps = {
  /** Wariant wizualny przycisku */
  variant?: "primary" | "secondary";
  /** Blokuje kliknięcie i zmienia wygląd */
  disabled?: boolean;
  children: React.ReactNode;
};

Dlaczego w tabeli nie widać propsów mojego komponentu?

Najczęstszy powód to ograniczenia domyślnego parsera, który czyta typy komponentów. Programiści zgłaszają to szczególnie przy komponentach pochodzących z bibliotek, takich jak MUI. Zanim zmienisz konfigurację, sprawdź, czy komponent jest wyeksportowany jako zwykła funkcja z jawnie opisanym typem propsów. Dopiero gdy to nie pomaga, przełącz parser w .storybook/main.ts:

const config: StorybookConfig = {
  // ...pozostała konfiguracja
  typescript: {
    reactDocgen: "react-docgen-typescript",
    reactDocgenTypescriptOptions: {
      tsconfigPath: "./tsconfig.app.json",
    },
  },
};

Opcja tsconfigPath jest ważna w projekcie ze świeżego szablonu Vite. Główny tsconfig.json tylko odsyła tam do innych plików, więc bez wskazania tsconfig.app.json parser działa, ale tabela propsów pozostaje bez opisów i bez typów (w moim teście zostały same propsy wywnioskowane z args). Ten parser lepiej rozumie typy TypeScript, ale zwykle wolniej startuje, więc włączaj go dopiero wtedy, gdy domyślny nie wystarcza i prostsze poprawki nie pomogły. Uwaga na wersję TypeScriptu: z TypeScript 7 (natywna wersja kompilatora) build kończy się błędem Cannot read properties of undefined (reading 'fileExists'). Działa z TypeScript 5.x.

Jak zadbać o wspólną konfigurację Vite w Storybooku?

Storybook z @storybook/react-vite uruchamia własną instancję Vite. Zwykle bierze ustawienia z vite.config, ale gdy potrzebujesz czegoś dodatkowego, np. aliasu importów, nadpisz konfigurację w viteFinal:

import path from "node:path";
import { mergeConfig } from "vite";

const config: StorybookConfig = {
  // ...pozostała konfiguracja
  viteFinal: async (viteConfig) =>
    mergeConfig(viteConfig, {
      resolve: {
        alias: { "@": path.resolve(process.cwd(), "src") },
      },
    }),
};

mergeConfig łączy Twoje ustawienia z domyślnymi, zamiast je zastępować. Dzięki temu komponent, który importuje @/utils, działa w Storybooku tak samo jak w aplikacji.

Jak dodać globalne style i motyw do wszystkich stories?

Komponenty z design systemu zwykle potrzebują globalnych tokenów CSS lub dostawcy motywu. Zamiast powtarzać to w każdym pliku, ustaw je raz w .storybook/preview.tsx:

import type { Preview } from "@storybook/react-vite";
import "../src/styles/tokens.css";

const preview: Preview = {
  parameters: {
    layout: "centered",
  },
  decorators: [
    (Story) => (
      <div style={{ padding: "1rem" }}>
        <Story />
      </div>
    ),
  ],
};

export default preview;

Importujesz tu plik z tokenami (tokens.css), a dekorator opakowuje każdą story. Jeśli aplikacja używa kontekstu, np. motywu lub routera, dodasz go właśnie w dekoratorze. Brak takiego kontekstu to częsta przyczyna błędów typu „hook zwraca null" tylko w Storybooku.

Czy Storybook się opłaca, a kiedy to przerost formy?

Storybook zwraca się, gdy komponentów używa kilka osób lub kilka aplikacji, a designer chce je oglądać bez uruchamiania produktu. W małym, jednoosobowym projekcie szybciej zrobisz to w samej aplikacji i dodatkowe narzędzie niczego nie ułatwi. Podobnie jest z pojedynczymi komponentami: jeśli komponent ma jedno miejsce użycia w aplikacji, story do niego to dublowanie pracy.

Stories bywają też bazą pod automatyczne testy komponentów. Jeśli interesuje Cię ta ścieżka, zobacz testowanie komponentów w React z Testing Library.

Co zapamiętać?

  • Storybook uruchamiasz poleceniem npm create storybook@latest, a komponenty opisujesz w plikach *.stories.tsx.
  • meta opisuje komponent, a każdy eksportowany obiekt to jedna story z własnymi args.
  • argTypes doprecyzowuje kontrolki, a control: false w jednej story ukrywa kontrolkę tylko tam.
  • Dla komponentów ze stanem użyj render, żeby dołożyć useState, a dla wielu wariantów naraz jednej story z kilkoma instancjami.
  • tags: ["autodocs"] generuje dokumentację, ale jakość tabeli zależy od typów i komentarzy w kodzie.
  • Globalne style i kontekst ustawiasz raz w .storybook/preview.tsx.

Jeśli chcesz nauczyć się budować i dokumentować komponenty w praktyce, z code review i wsparciem mentora, zobacz naukę frontendu z mentorem.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force