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”.
