Projeto final: lib de UI com 5+ componentes, tokens, Storybook publicado
6 min de leitura
Hora de unir tudo. Você vai construir uma lib de UI própria (5+ componentes: Button, Input, Card, Badge, Modal), com tokens em Style Dictionary, theming light/dark, Storybook com autodocs + MDX, test runner + a11y addon, Changesets pra versionamento, e publicação automatica no npm via CI. É o ciclo completo de um design system: tokens, componentes, docs, testes, release.
Esse projeto não segue o esqueleto de 3 camadas
dos outros nós. É um brief de projeto, no estilo
de projects/<slug>.mdx do aprenda-community. Lê
até o fim antes de começar.
O que você vai construir
Uma lib de UI chamada @sua-org/ui (ou nome
que preferir) com:
- 5+ componentes - Button, Input, Card, Badge, Modal (mínimo). Cada um com stories, args ricos, e doc MDX.
- Tokens - Style Dictionary com primitive
- semantic, export pra CSS variables, suporte
a light/dark via
data-theme.
- semantic, export pra CSS variables, suporte
a light/dark via
- Storybook - CSF3 stories pra cada componente, autodocs ligado, MDX pra páginas de overview ("Introdução", "Tokens", "Padrões").
- Test runner + a11y -
playfunctions nos componentes interativos, addon a11y com regras WCAG 2 AA. - Changesets - workflow de release com PR "Version Packages" + publish no GitHub Packages (ou npm).
- CI - GitHub Actions rodando lint, typecheck, test runner, e (opcional) Chromatic.
A escolha do "domínio" (lib de botão + form vs
lib de cards + data display) é sua. O esqueleto
dado é o caminho feliz, desvie quando
precisar e anote as decisões no
editorial-decisions.md da trilha.
Objetivo
- Consolidar Storybook + CSF3 + autodocs + MDX num projeto real.
- Praticar tokens em 3 níveis (primitive, semantic, component) com Style Dictionary.
- Ver theming (light/dark) funcionando via
data-themeem CSS variables. - Configurar test runner + a11y addon, com
1+
playfunction por componente interativo. - Publicar via Changesets + GitHub Actions.
- Subir o Storybook publicado (Chromatic ou self-host) e mostrar a badge.
Requisitos (mínimo)
Setup inicial:
-
pnpm create vite@latest minha-ui -- --template react-ts(Vite + React + TS). -
pnpm dlx storybook@latest init- detecta Vite, instala addons (a11y, docs, controls). -
pnpm add -D style-dictionary @changesets/cli @storybook/test-runner @testing-library/jest-dom @testing-library/user-event vitest @vitest/coverage-v8 jsdom chromatic. - Estrutura sugerida:
src/components/<Component>/<Component>.tsxsrc/components/<Component>/<Component>.stories.tssrc/components/<Component>/index.tssrc/tokens/{colors,spacing,typography}.jsonsrc/theme/ThemeProvider.tsxstyle-dictionary.config.jsvitest.config.ts,vitest.setup.ts.storybook/{main,preview}.ts.changeset/config.json.github/workflows/{test,chromatic,changesets}.yml
Tokens (Style Dictionary):
-
src/tokens/colors.jsoncom primitive (blue/gray/success/error families). -
src/tokens/semantic.jsoncom semantic (color-primary, color-bg, color-fg, etc). -
style-dictionary.config.jscom 2 platforms:css(geradist/tokens.css) ejs(geradist/tokens.jscom objeto). -
dist/tokens.cssexportado como@sua-org/ui/tokens.css(viaexportsno package.json). - Theming light/dark com
:roote[data-theme="dark"]no CSS gerado.
Componentes (mínimo 5):
- Button - variants (primary, secondary,
danger, ghost), size (sm, md, lg), loading,
disabled. CSS variables.
aria-busyquando loading. - Input - label, placeholder, error
message, disabled.
aria-invalidquando error.aria-describedbyligando help/error. - Card - compound component
(
<Card>,<Card.Header>,<Card.Body>,<Card.Footer>). Variants (elevated, outlined). - Badge - variants (neutral, success, warning, danger). Size (sm, md).
- Modal - slot pattern (
<Modal.Trigger>,<Modal.Content>,<Modal.Title>,<Modal.Description>,<Modal.Actions>). Focus trap, ESC fecha, ARIA correto.
Storybook (CSF3 + autodocs + MDX):
- Cada componente tem
*.stories.tscom CSF3,tags: ["autodocs"], eargTypesrico (comdescription). - Cada componente interativo (Button, Input,
Modal) tem pelo menos 1 story com
playfunction. - Pelo menos 1 página MDX (ex:
Intro.mdx) com overview da lib. -
preview.tscom decorator global de ThemeProvider. - Addon a11y configurado (WCAG 2 AA).
Test runner + a11y:
-
pnpm test-storybookroda todas as stories em browser real. -
playfunction de Button verifica queonClické chamado ao clicar. -
playfunction de Input verifica queonChangeé chamado ao digitar. - Addon a11y roda axe-core em cada story.
Build falha se violação
criticalouseriousfor detectada.
Versionamento (Changesets):
-
.changeset/config.jsoncom config padrão (fixed: []pra uma package, ou["packages/*"]se monorepo). - GitHub Action
changesets/action@v1configurada pra abrir Version PR. - Version PR bump versão em
package.json, atualizaCHANGELOG.md, e (com secret configurado) publica no npm. - Documento
.changeset/README.mdexplicando o fluxo pro time.
Build + publish:
-
pnpm buildgeradist/com:dist/index.js(ESM + CJS via tsup ou rollup).dist/index.d.ts(tipos TS).dist/tokens.css(output Style Dictionary).
-
package.jsoncomexportsfield apontando prodist/. -
peerDependenciescorretas (react, react-dom). -
filesfield listandodist,README.md,LICENSE. - Workflow de publish no GitHub Actions (manual trigger ou via Changesets).
Documentação:
-
README.mdda lib com:- Instalação.
- Uso básico (1 exemplo de cada componente).
- "Customização de tema" (como sobrescrever tokens).
- Links pra Storybook publicado.
-
LICENSE(MIT ou a da empresa). -
CHANGELOG.md(gerado por Changesets).
CI (GitHub Actions):
- Workflow
test.yml:- lint + typecheck + vitest em paralelo (rápido).
- test-storybook depois (browser real).
- Workflow
chromatic.yml(opcional, mas recomendado):- Publica Storybook no Chromatic.
- Comenta no PR com diff visual.
- Workflow
changesets.yml:- Em push na main, roda
changeset version- cria PR.
- Em PR merge na main, roda
changeset publish(com secret).
- Em push na main, roda
Desafios extras (stretch goals)
Se você terminou o mínimo e quer ir além:
- Compound component para Select / Combobox com Radix Primitives (lógica de a11y/keyboard de graça).
- Theme provider com toggle de light/dark (botão que troca o tema, persistindo em localStorage).
- Storybook publicado no Chromatic com
badge no README (
[]()). - Codemod de migração (com
jscodeshift)- ex: renomear
variant="primary"emintent="primary"automaticamente.
- ex: renomear
- Pre-release versioning -
pnpm changeset pre enter alphapra testar 2.0.0-alpha antes do 2.0.0 final. - Visual review com Chromatic - PR com
mudança em
<Button>mostra diff visual, time revisa. - i18n de strings nos componentes (botão de loading com texto traduzido).
- Publicação no GitHub Packages (registry privado) em vez de npm público.
- Storybook com Viewport add-on pra testar em mobile/tablet/desktop no dev.
- Tokens exportados como JS (além de CSS) - pra uso em React Native (sem CSS).
- Theme dark automático baseado em
prefers-color-scheme(sem precisar de JS para detectar). - Play function avançada em Modal - simula ESC fecha, click outside fecha, focus trap funciona.
Dicas
Por onde começar:
- Tokens primeiro -
src/tokens/*.json+style-dictionary.config.js+pnpm build. Sem tokens, nenhum componente consegue consumir. - Button - o primitive mais usado, ensina o pattern (variants, sizes, states).
- Storybook do Button - stories CSF3, autodocs, argTypes. Sem doc, ninguém descobre.
- Mais componentes (Input, Badge, Card) - incrementais.
- Modal - composto, mais complexo, é o último porque depende dos patterns.
- Theming light/dark - depois que os componentes estão prontos, aplicar tema.
- Test runner + a11y - depois de cada componente.
- Changesets + CI - final.
Armadilhas comuns:
- Pular Style Dictionary e hardcodar tokens no CSS - parece mais simples, mas perde geração automática pra iOS/Android, e refatoração quebra.
- Não exportar tokens como CSS - precisa
ser consumível por outros apps.
exportsfield no package.json resolve. - Theme como prop em vez de Context -
<Button theme="dark">em vez dedata-theme="dark"no root. Funciona mas não escala (todo componente precisa saber do tema). - Versionar manualmente -
npm version majorna mão, sem Changesets. Funciona pra uma release, falha em escala. - Esquecer de bloquear
peerDependencies- DS publica com
react@18em dependencies, consumidor tem 2 Reacts. App quebra.
- DS publica com
exportsfield errado - consumidor importa@sua-org/uie recebeundefined. Testar o build em um app externo antes de publicar.- Publicar Storybook com token sensível
no bundle - tokens de API key, URL
interna, etc. Revisar
.npmignoreefilesfield. - Snapshot de DOM - usar visual review do Chromatic em vez. Snapshot de DOM é frágil.
- Esquecer
aria-busyem Button loading - leitor de tela não sabe que tá carregando. Addon a11y pega.
Como validar que terminou:
-
pnpm buildroda sem erro. -
dist/index.js+dist/tokens.cssexistem e importam limpo num app externo de teste. -
pnpm test-storybookroda verde (todas as stories, todos os testes de a11y). - Adicionar changeset manualmente, push na main, ver Version PR abrir e publicar no npm (ou GitHub Packages).
-
pnpm dlx storybook buildgera Storybook estático,pnpm dlx http-server storybook-staticmostra localmente. - Instalação em app externo via
pnpm add @sua-org/ui@1.0.0funciona,import { Button } from "@sua-org/ui"renderiza corretamente. - Theme dark funciona:
document.documentElement.dataset.theme = "dark"muda os componentes automaticamente.
Leituras que ajudam durante o projeto:
- Storybook - Publish Storybook - workflow integrado.
- Style Dictionary - Quick start - setup em 5 min.
- Chromatic - Publish - publicar Storybook.
O projeto final é onde a trilha vira "sua". As escolhas de ferramenta (Style Dictionary vs Tokens Studio, Chromatic vs Percy, Changesets vs Lerna), de theming (data-theme vs class), de composição (compound vs headless) são todas suas. O esqueleto dado é o caminho feliz, desvie quando precisar, e anote as decisões no
editorial-decisions.mdda trilha.