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:

  1. Instalação:
npx sb init --builder webpack5 --type react
  1. 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”.