1. Introdução
O Atomic Design, proposto por Brad Frost, é uma metodologia que traz disciplina e previsibilidade ao desenvolvimento de sistemas de design. Ao quebrar a interface em “átomos”, “moléculas”, “organismos”, “templates” e “páginas”, sua equipe ganha:
- Escalabilidade, criando componentes que podem ser combinados de formas inesperadas.
- Consistência, ao ter uma linguagem visual e de comportamento unificada.
- Manutenibilidade, pois alterações em um átomo refletem em toda a aplicação.
Neste artigo, vamos aprofundar cada etapa do Atomic Design aplicado a um projeto React + Tailwind CSS, incluindo:
- Estrutura de pastas e nomenclatura
- Exemplo completo de design tokens
- Exemplos de testes e documentação com Storybook
- Boas práticas e armadilhas comuns
2. Estrutura de Pastas e Naming Conventions
Organizar bem seu repositório é tão importante quanto a estrutura de componentes. Uma sugestão:
src/ ├── tokens/ # Design tokens (cores, tipografia, espaçamentos) │ └── index.ts ├── components/ │ ├── atoms/ # Componentes básicos (Button, Input, Icon) │ ├── molecules/ # Combinações de átomos (SearchField, FormGroup) │ ├── organisms/ # Conjuntos de moléculas (Header, Footer) │ ├── templates/ # Layouts sem dados (MainTemplate) │ └── pages/ # Páginas com dados reais (HomePage) ├── hooks/ # Hooks customizados ├── utils/ # Funções utilitárias └── stories/ # Configuração e stories do Storybook
Convenções de nomenclatura:
- Átomos devem ter nomes genéricos: Button, Input, Avatar.
- Moléculas descrevem função: SearchField, ModalHeader.
- Organismos descrevem áreas: SiteHeader, ProductCardGrid.
- Templates indicam estrutura: DefaultTemplate, DashboardTemplate.
- Pages indicam rota ou seção: HomePage, ProductPage.
3. Design Tokens
Design tokens são variáveis que mantêm seu sistema coerente. Em "src/tokens/index.ts":
export const colors = { primary: '#1D4ED8', primaryDark: '#1E40AF', secondary: '#6B7280', background: '#F9FAFB', text: '#111827', }; export const spacing = { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', }; export const typography = { fontFamily: "'Inter', sans-serif", fontSizes: { sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', }, };
No "tailwind.config.js", importe tokens:
const { colors, spacing, typography } = require('./src/tokens'); module.exports = { theme: { extend: { colors, spacing: spacing, fontFamily: { sans: typography.fontFamily }, fontSize: typography.fontSizes, }, }, content: ['./src/**/*.{js,jsx,ts,tsx}'], };
4. Átomos
4.1 Button
// src/components/atoms/Button.tsx import React from 'react'; import { colors, spacing } from '../../tokens'; export interface ButtonProps { children: React.ReactNode; onClick?: () => void; variant?: 'primary' | 'secondary'; } export const Button: React.FC<ButtonProps> = ({ children, onClick, variant = 'primary', }) => ( <button onClick={onClick} className={` px-${spacing.md} py-${spacing.sm} rounded ${variant === 'primary' ? `bg-${colors.primary} text-white hover:bg-${colors.primaryDark}` : `bg-${colors.secondary} text-white hover:opacity-90`} `} > {children} </button> );
Teste de Átomo (Jest + React Testing Library)
// src/components/atoms/Button.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from './Button'; test('dispara onClick e exibe o texto', () => { const handleClick = jest.fn(); render(<Button onClick={handleClick}>Clique</Button>); const btn = screen.getByText('Clique'); fireEvent.click(btn); expect(handleClick).toHaveBeenCalledTimes(1); });
5. Moléculas
5.1 SearchField
// src/components/molecules/SearchField.tsx import React, { useState } from 'react'; import { Input } from '../atoms/Input'; import { Button } from '../atoms/Button'; interface SearchFieldProps { placeholder?: string; onSearch: (term: string) => void; } export const SearchField: React.FC<SearchFieldProps> = ({ placeholder = 'Buscar...', onSearch, }) => { const [term, setTerm] = useState(''); return ( <div className="flex space-x-2"> <Input value={term} onChange={e => setTerm(e.target.value)} placeholder={placeholder} /> <Button onClick={() => onSearch(term)}>Ir</Button> </div> ); };
6. Organismos
6.1 SiteHeader
// src/components/organisms/SiteHeader.tsx import React from 'react'; import { Logo } from '../atoms/Logo'; import { SearchField } from '../molecules/SearchField'; import { Button } from '../atoms/Button'; export const SiteHeader: React.FC = () => ( <header className="flex items-center justify-between p-4 bg-white shadow"> <Logo /> <SearchField onSearch={term => console.log('Buscar por', term)} /> <Button variant="secondary">Login</Button> </header> );
7. Templates
7.1 DefaultTemplate
// src/components/templates/DefaultTemplate.tsx import React from 'react'; import { SiteHeader } from '../organisms/SiteHeader'; import { Footer } from '../organisms/Footer'; export const DefaultTemplate: React.FC<{ children: React.ReactNode }> = ({ children }) => ( <div className="flex flex-col min-h-screen"> <SiteHeader /> <main className="flex-1 container mx-auto p-4">{children}</main> <Footer /> </div> );
8. Páginas
8.1 HomePage
// src/pages/HomePage.tsx import React from 'react'; import { DefaultTemplate } from '../components/templates/DefaultTemplate'; import { ProductCardGrid } from '../components/organisms/ProductCardGrid'; export const HomePage: React.FC = () => { // imagine fetch de API aqui const products = [{ id: 1, name: 'Produto A' }, { id: 2, name: 'Produto B' }]; return ( <DefaultTemplate> <h1 className="text-3xl font-bold mb-6">Bem-vindo à nossa loja</h1> <ProductCardGrid items={products} /> </DefaultTemplate> ); };
9. Documentação e Storybook
Para alinhar designers e desenvolvedores, configure o Storybook:
- Instalação:
npx sb init --builder webpack5 --type react
- Exemplo de story ("src/stories/Button.stories.tsx"):
import React from 'react'; import { Button } from '../components/atoms/Button'; export default { title: 'Atoms/Button', component: Button, argTypes: { variant: { control: 'radio', options: ['primary', 'secondary'] }, }, }; const Template = args => <Button {...args}>Exemplo</Button>; export const Primary = Template.bind({}); Primary.args = { variant: 'primary' }; export const Secondary = Template.bind({}); Secondary.args = { variant: 'secondary' };
Isso ajuda a testar visualmente estados e a gerar documentação automática.
10. Vantagens e Desvantagens
Vantagens:
- 🚀 Reutilização máxima: alterações em átomos se propagam automaticamente.
- 🎨 Consistência visual: design tokens garantem uniformidade.
- 👥 Escala colaborativa: designers e devs falam a mesma linguagem.
- 🔍 Testabilidade isolada: cada componente pode ser testado independentemente.
Desvantagens:
- 🧩 Curva de aprendizado: entender cada nível leva tempo.
- ⚙️ Overhead estrutural: pastas e arquivos extras em projetos pequenos.
- 🛠️ Manutenção de abstrações: atualização de tokens ou átomos exige cuidado.
- 📂 Complexidade de navegação: muitas camadas de pastas para encontrar o que precisa.
11. Boas Práticas
- Mantenha os átomos “puros”, sem lógica de negócio.
- Use design tokens para evitar “hard codes” de cor, tipografia e espaçamento.
- Documente no Storybook todos os estados relevantes (hover, disabled, loading).
- Escreva testes unitários focados em props e callbacks.
- Revisite periodicamente: conforme o produto cresce, refatore componentes que se tornarem muito grandes ou duplicados.
12. Conclusão
Adotar o Atomic Design em React + Tailwind traz clareza, velocidade no desenvolvimento e confiança na consistência visual. Com a estrutura certa de pastas, design tokens bem definidos e documentação via Storybook, sua equipe estará pronta para escalar o sistema de UI sem medo de “quebrar nada”.