Atomic Design is a useful lens. It is a terrible folder structure. I stopped enforcing it literally, and my own codebases have been easier to navigate ever since.
Where the Mapping Breaks Down
The five-level hierarchy makes intuitive sense in a conference slide. In practice, the boundaries blur on day one. Is a TextField an atom? It contains a label, an input, a helper text span, and an error message. That is four elements. By strict Atomic rules it is a molecule. But conceptually it is a single primitive, and treating it as a molecule means it lives in a different folder than your other form primitives.
DataTable is the same problem at the organism level. Is pagination part of the table organism or a separate organism composed alongside it? The answer changes depending on whether you abstract the table as a standalone component or always pair it with controls. Neither answer is wrong. The taxonomy just does not give you a rule.
The second failure mode is that strict Atomic Design treats atoms as stateless. Angular's component model is built around behavior. A Button without a loading state or a disabled state is not a useful atom in a financial product. Pushing that behavior up to the molecule layer means simple things become unnecessarily complicated, and you end up with molecule-level abstractions that exist only to hold state that belonged on the atom.
// 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(' ');
}
}The Three-Layer Model That Actually Works
I use three layers instead of five. Design system layer: presentational components that are reusable across any project. Feature layer: domain-specific components reusable within the current project. Page layer: smart components at the route level that compose the other two.
This maps straight onto the folder and library structure of a standalone Angular project. No NgModules to align it against, just three directories and a rule about which way the imports are allowed to point. New developers understand it on the first day. The question they ask, "does this belong to the design system or to the transactions feature?", is much easier to answer than "is this a molecule or an organism?".
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 Still Has a Role in Figma
In Figma, the vocabulary is more useful because Figma components do not carry business logic. Organizing a Figma library with atoms, molecules, and organisms as naming conventions works fine. The mistake is demanding a 1:1 mapping to the Angular structure. The Figma library represents the visual system. The Angular library represents the technical implementation. They stay aligned through token names and component naming conventions, not folder structure parity.
At Wonderlabs I maintained both the Figma component library and the Angular component library for the credit card app. We agreed on names early: PascalCase, noun-first, no generic names. A component called Card in Figma that is actually a SpendingLimitCard in every usage should be named SpendingLimitCard in both places. That one convention prevented more handoff confusion than any structural methodology ever did. Later, at BMO, I worked with the bank's design system, owned by another team, which put me on the consuming side. The rule matters just as much from there: whenever a Figma name and an Angular selector match, nobody needs a translation table.
Use Atomic Design to think about your system, not to organize your folders. The taxonomy is valuable. The strict hierarchy usually is not.
The Real Test
The only question worth asking about a component architecture is: can a developer who joined last week find the right file in under 30 seconds, and know immediately what that file is responsible for? If they can, your structure is working. If they are debating whether something is a molecule or an organism, you have optimized for the taxonomy instead of the outcome.
- Design system layer: stateless or minimally stateful, no domain logic, reusable across any product
- Feature layer: domain-aware, reusable within the feature domain, can use design system components freely
- Page layer: smart, route-level, one per route, composes from both layers, connects to state management
- Naming: PascalCase, noun-first, specific over generic. SpendingLimitCard, not Card
- Figma and Angular names should match. Run a naming alignment session before anyone opens a tool
References
- 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