Atomic Design é uma lente útil. É uma estrutura de pastas terrível. Parei de impor literalmente, e minhas próprias codebases ficaram mais fáceis de navegar desde então.
Onde o Mapeamento Quebra
A hierarquia de cinco níveis faz sentido intuitivo em um slide de conferência. Na prática, as fronteiras ficam borradas no primeiro dia. Um TextField é um átomo? Ele contém um label, um input, um span de texto auxiliar e uma mensagem de erro. São quatro elementos. Pelas regras estritas do Atomic, é uma molécula. Mas conceitualmente é uma primitiva única, e tratá-lo como molécula significa que ele vive em uma pasta diferente dos seus outros primitivos de formulário.
DataTable tem o mesmo problema no nível de organismo. A paginação faz parte do organismo da tabela ou é um organismo separado composto junto? A resposta muda dependendo de se você abstrai a tabela como um componente standalone ou sempre a associa com controles. Nenhuma resposta está errada. A taxonomia simplesmente não te dá uma regra.
O segundo modo de falha é que o Atomic Design estrito trata átomos como stateless. O modelo de componentes do Angular é construído em torno de comportamento. Um Button sem estado de loading ou estado de disabled não é um átomo útil em um produto financeiro. Empurrar esse comportamento para a camada de molécula significa que coisas simples ficam desnecessariamente complicadas, e você acaba com abstrações no nível de molécula que existem apenas para guardar estado que pertencia ao átomo.
// A Button that carries its own state, and should
import { Component, Input } from '@angular/core';
import { SpinnerComponent } from '../spinner/spinner.component';
@Component({
selector: 'ds-button',
imports: [SpinnerComponent],
template: `
<button
[class]="buttonClasses"
[disabled]="disabled || loading"
[attr.aria-busy]="loading"
>
@if (loading) {
<ds-spinner size="sm" />
}
<ng-content />
</button>
`
})
export class ButtonComponent {
@Input() variant: 'primary' | 'secondary' | 'ghost' = 'primary';
@Input() size: 'sm' | 'md' | 'lg' = 'md';
@Input() disabled = false;
@Input() loading = false;
get buttonClasses(): string {
return [
'btn',
`btn--${this.variant}`,
`btn--${this.size}`,
this.loading ? 'btn--loading' : ''
].filter(Boolean).join(' ');
}
}O Modelo de Três Camadas Que Realmente Funciona
Eu uso três camadas ao invés de cinco. Camada de design system: componentes presentacionais reutilizáveis em qualquer projeto. Camada de feature: componentes específicos do domínio reutilizáveis dentro do projeto atual. Camada de página: componentes smart no nível da rota que compõem as outras duas.
Isso encaixa direto na estrutura de pastas e bibliotecas de um projeto Angular standalone. Nenhum NgModule pra alinhar, só três diretórios e uma regra sobre pra que lado os imports podem apontar. Desenvolvedores novos entendem no primeiro dia. A pergunta que eles fazem, "isso pertence ao design system ou à feature de transações?", é muito mais fácil de responder do que "isso é uma molécula ou um organismo?".
src/
lib/ # Design system layer (publishable library)
components/
button/
form-field/
data-table/
navigation-bar/
tokens/
styles/
features/
transactions/
components/
transaction-card/ # Domain-specific, not reusable outside transactions
transaction-filter/
pages/
transaction-list-page/
transaction-detail-page/
accounts/
components/
account-summary/
pages/
accounts-overview-page/Atomic Design Ainda Tem um Papel no Figma
No Figma, o vocabulário é mais útil porque componentes Figma não carregam lógica de negócio. Organizar uma biblioteca Figma com átomos, moléculas e organismos como convenções de nomenclatura funciona bem. O erro é exigir um mapeamento 1:1 para a estrutura Angular. A biblioteca Figma representa o sistema visual. A biblioteca Angular representa a implementação técnica. Elas se mantêm alinhadas através de nomes de tokens e convenções de nomenclatura de componentes, não por paridade de estrutura de pastas.
Na Wonderlabs eu mantinha tanto a biblioteca de componentes Figma quanto a biblioteca de componentes Angular para a app de cartão de crédito. Concordamos em nomes cedo: PascalCase, substantivo primeiro, sem nomes genéricos. Um componente chamado Card no Figma que na verdade é um SpendingLimitCard em todo uso deveria se chamar SpendingLimitCard nos dois lugares. Essa única convenção evitou mais confusão de handoff do que qualquer metodologia estrutural. Mais tarde, no BMO, trabalhei com o design system do banco, mantido por outro time, o que me colocou do lado consumidor. A regra vale igual desse lado: sempre que o nome no Figma e o seletor Angular batem, ninguém precisa de uma tabela de tradução.
Use Atomic Design para pensar sobre seu sistema, não para organizar suas pastas. A taxonomia é valiosa. A hierarquia estrita geralmente não é.
O Teste Real
A única pergunta que vale a pena fazer sobre uma arquitetura de componentes é: um desenvolvedor que entrou na semana passada consegue encontrar o arquivo certo em menos de 30 segundos, e saber imediatamente qual é a responsabilidade daquele arquivo? Se consegue, sua estrutura está funcionando. Se está debatendo se algo é uma molécula ou um organismo, você otimizou para a taxonomia ao invés do resultado.
- Camada de design system: stateless ou com estado mínimo, sem lógica de domínio, reutilizável em qualquer produto
- Camada de feature: consciente do domínio, reutilizável dentro do domínio da feature, pode usar componentes do design system livremente
- Camada de página: smart, nível de rota, uma por rota, compõe a partir das duas camadas, conecta ao state management
- Nomenclatura: PascalCase, substantivo primeiro, específico ao invés de genérico. SpendingLimitCard, e não Card
- Nomes no Figma e Angular devem ser iguais. Faça uma sessão de alinhamento de nomenclatura antes de qualquer um abrir uma ferramenta
Referências
- Brad Frost. Atomic Web Design. https://bradfrost.com/blog/post/atomic-web-design/
- Angular libraries guide. https://angular.dev/guide/libraries
- Nx module boundaries. https://nx.dev/concepts/decisions/project-dependency-rules