Skip to contentSkip to menuSkip to footer

Search the site

Enter at least 3 non-space characters to see suggested results.

Top results

Type 3 characters to see results.

Healthy Websites Have Good Anatomy

How Atomic Design creates clear website anatomy, modularity, and a methodology that future contributors can pick up on with minimal context.

Web Dev

Healthy Websites Have Good Anatomy

A website looks flat on a screen. Underneath, it has an anatomy: small parts, groups of parts, larger systems, and a few decisions held together by CSS and optimism.

Web developers have plenty of words for those parts. Elements. Modules. Features. Components. Put three developers in a room and we can spend half a meeting agreeing enthusiastically while imagining three different things.

Atomic Design takes the slightly strange approach of naming interface parts after chemistry. It sounds like someone opened the periodic table while naming folders, but the metaphor solves a real communication problem. Everyone understands that an atom is small, a molecule combines atoms, and an organism is made from many smaller systems. The terms give us a shared picture of scale before anyone opens the code.

That picture is the basis for how I organize components, name classes, and decide which part of a website should own what.

Atomic Design

Brad Frost's Atomic Design methodology describes five stages:

  1. Atoms — the smallest reusable interface elements
  2. Molecules — focused components made from smaller parts
  3. Organisms — major interface regions that house those smaller components
  4. Templates — page-level structures that arrange the regions
  5. Pages — real instances of templates with actual content

The stages describe a hierarchy, not a five-step production line:

Atomic design is not a linear process, but rather a mental model…

I don't finish every atom, ring a tiny laboratory bell, and grant the team permission to begin molecules. The layers inform one another. A page exposes what an organism needs; the organism reveals a missing molecule; the molecule tells me an atom needs another state.

Atomic Design isn't, by itself, a CSS or JavaScript architecture. I use its mental model as one: if the hierarchy helps me reason about the interface, it can also help me organize the project.

In my implementation, an organism is generally a main page section: a Header, an Image + Content section, or Footer. I avoid putting organisms inside other organisms. You wouldn't normally find a monkey inside a lion, and I don't want the component tree creating that kind of zoological nightmare fuel either. If a reusable component sits inside an organism, it usually belongs at the molecule level.

Templates and Pages step outside the anatomy metaphor, but that is intentional. Stretching the biology further would make the vocabulary more complicated than the problem it solves. The analogy is a tool, not a legally binding treaty with nature.

A layered website showing an atom inside a molecule, an organism, a template, and a page

Each layer grows in responsibility, from a single atom to a complete page.

Prefixes Map the Project

I commonly let Atomic Design answer two questions at once:

  • What kind of responsibility does this component have?
  • Where should another developer expect to find it?

The directories mirror the hierarchy:

src/components
atoms/
  Button/
  Tag/
molecules/
  SearchForm/
  PostCard/
organisms/
  Header/
  PostFeed/
templates/
  ArchiveLayout/
  ArticleLayout/

The class prefix repeats that map in the browser:

PrefixRoleExampleLikely home
a-Atoma-button/components/atoms/Button
m-Moleculem-post-card/components/molecules/PostCard
o-Organismo-post-feed/components/organisms/PostFeed
t-Template or content typet-archive-layout, t-blog-post/components/templates or template-level styles
p-One pagep-blogpage- or slug-specific styles
u-Utilityu-container, u-sr-onlyshared utility styles

The prefixes are intentionally boring. Boring is excellent here. A future developer shouldn't need an illuminated manuscript to learn that m-post-card is a molecule in the molecules folder.

BEM Defines Relationships

Atomic Design tells me the scale of a component. BEM supplies the grammar: m-post-card is the block, m-post-card__button is an element it owns, and m-post-card--featured is a variation. The Atomic prefix tells me the scale; BEM tells me the relationship.

Ownership Defines Responsibility

The most useful thing Atomic Design gives me is a clear way to reason about ownership.

An atom owns what is true about itself everywhere. A Button can own its typography, padding, border, color, and interaction states because it needs those qualities wherever it appears.

The Button shouldn't own where it's positioned inside a PostCard. It won't always appear there, so its base styles shouldn't carry that assumption. The PostCard owns the context and should decide the Button's margin, alignment, or width.

This becomes clearer in a complete example:

A PostFeed organism housing PostCard molecules
<section class="o-post-feed">
  <div class="o-post-feed__container u-container">
    <div class="o-post-feed__posts">
      <article class="o-post-feed__post m-post-card">
        <h3 class="m-post-card__title">Post Title</h3>
        <p class="m-post-card__excerpt">Post excerpt</p>
        <a href="/post" class="m-post-card__button a-button">Read More</a>
      </article>
    </div>
  </div>
</section>

Each level has a job:

  • o-post-feed is the organism—the outer housing for the feed.
  • m-post-card is a molecule that owns its title, excerpt, and call to action.
  • a-button is an atom that gives the link its reusable Button appearance.

The link has both m-post-card__button and a-button because those classes answer different questions. a-button owns what every Button looks and feels like. m-post-card__button owns what happens to that Button inside a Post Card.

Intrinsic style versus contextual style
.a-button {
  padding: 10px 16px;
  border-radius: 999px;
}
 
.m-post-card {
  &__excerpt {
    color: var(--text-color--muted);
  }
 
  &__button {
    margin-top: 16px;
  }
}
 
.o-post-feed {
  &__posts {
    display: grid;
    gap: 24px;
  }
 
  &__post {
    height: 100%;
  }
}

Putting margin-top on .a-button would make every Button inherit a PostCard-specific assumption. The m-post-card__button class keeps that decision with the component that actually needs it. Likewise, the feed, not the Post Card, owns the grid and how each Post Card is positioned within it.

This also avoids the BEM grandchild trap. Suppose I reach for .m-post-card__button__icon to style the Icon inside a Button. That class pretends the PostCard owns the Button's internal anatomy. It doesn't. The PostCard owns the Button's contextual placement through .m-post-card__button, while the Button owns its Icon through .a-button__icon. If the PostCard truly needs to adjust that Icon in this context, I can express the relationship with .m-post-card__button .a-button__icon without inventing grandchild ownership. The classes get clearer because the boundary gets clearer—not because we simply grew tired of typing underscores.

When to Split a Component

Atomic Design doesn't mean every heading, paragraph, and decorative span needs its own component. If changing one PostCard means opening several other files just to find its heading, you haven't built a design system—you've built an escape room.

Before I split something out, I ask:

  1. Will it be reused in another context with the same purpose?
  2. Does it have behavior, states, or accessibility rules worth owning independently?
  3. Should changes to it intentionally affect every place it's used?
  4. Does extracting it make ownership clearer rather than merely moving markup?

The heading inside m-post-card may need a particular font, size, and color. That doesn't automatically make it a Heading atom. If that styling only makes sense inside a PostCard, and changing it globally would be surprising, then m-post-card__heading is exactly where it belongs.

The same reasoning prevents unnecessary components like m-post-card-header when the header will only ever exist inside one PostCard. Splitting it out just to satisfy Atomic Design isn't helpful modularity. It's missing the forest for the trees.

Atomic Design helps me draw the boundary. BEM makes that ownership visible in the class names.

Archetypes Define Personality

I eventually needed a home for components that share essential behavior but not a visual identity, so I introduced archetypes.

An archetype isn't another Atomic Design tier. It sits underneath the styled components and gives them a personality without giving them an outfit.

  • Click says, “This is clicky.” A Button and a Link can share URLs, targets, click handlers, refs, and ARIA attributes.
  • Input says, “Someone can enter text here.” A TextInput and a Search component can share the literal input behavior and validation logic.
  • Card says, “This is card-like.” Several card components can share their structural API without sharing their visual design (i.e., an article tag with a consistent set of children).

These are the bones and behavior of a component type, not its CSS. The archetype keeps developers from copying the same internal machinery into every new variation and creates one source of truth for the parts that ought to remain consistent.

My rule of thumb is:

An archetype defines what a type of component involves and does. An atom, molecule, or organism defines what this component means and how it looks here.

That also keeps one “universal component” from acquiring 113 appearance props and the emotional range of a tax form.

Click as an Archetype

Click is the clearest example. A Button and a Link look different, but both need the same interactive contract: URL, target, ARIA attributes, disabled state, click handling, data attributes, and refs.

The archetype chooses the correct underlying element:

A simplified Click archetype
const Click = ({
  url,
  isStatic,
  staticTag = 'div',
  ariaLabel,
  children,
  ...attributes
}) => {
  const sharedAttributes = {
    'aria-label': ariaLabel,
    ...attributes,
  };
 
  if (isStatic) {
    const Tag = staticTag;
    return <Tag {...sharedAttributes}>{children}</Tag>;
  }
 
  if (url) {
    return <a href={url} {...sharedAttributes}>{children}</a>;
  }
 
  return <button type="button" {...sharedAttributes}>{children}</button>;
};

With that behavior centralized, the visual components can stay almost boring:

Button.js and Link.js
export const Button = props => (
  <Click className="a-button" {...props} />
);
 
export const Link = props => (
  <Click className="a-link" {...props} />
);

Both components share the same options, but the props determine their semantics:

<Link url="/about" target="_self">About</Link>
<Link isStatic>Coming soon</Link>
<Button onClick={openModal} aria-haspopup="dialog">Open modal</Button>
<Button isStatic>Unavailable</Button>

With a url, Click renders a link and maps that value to href. Without a URL, it renders a button, which is more appropriate for triggering an action such as opening a modal, and more importantly not pretending to be an <a> tag when it isn't (sorry, screen readers). With isStatic, it can render a plain div or another static tag that retains the Button or Link styling without being interactive.

Click owns the shared semantics and attributes. a-button and a-link own the appearance. Same personality; different outfits. If every clickable component needs a new ARIA prop, I add it to Click once instead of hunting through every clickable component I've ever made like an accessibility-themed scavenger hunt.

This is also why I don't need an archetype- class prefix in the rendered HTML. Archetypes are implementation architecture, not another visual styling scope. The consuming component owns the public class name.

Utilities Cross Boundaries

Utilities are the deliberate exception to component ownership. A u- class does one small, reusable job and is allowed to travel: u-container controls a content boundary, u-h1 applies a type treatment without changing the semantic meaning, and u-sr-only visually hides certain content while keeping it accessible to screen readers.

Utilities should remain narrow. The moment u-card-with-blue-border-and-special-hover appears, the utility has put on a fake mustache and is attempting to sneak back into the component gallery.

Templates and Pages Define Scope

At the wide end of the anatomy, I use t- for templates and content types. A blog post, guide, and portfolio project can share components while technically having different overall layouts.

A blog page might use p-blog t-archive. The template class supplies the general archive layout; the page class uses p- plus the page's slug to scope genuine exceptions to that page. I treat p- as a scalpel, not an excuse to add one more body.page-id-42 😉.

When page overrides keep multiplying, a reusable element is usually trying to get our attention.

Discover the Breakdown in the Browser

The biggest payoff appears after launch, when a developer who didn't build the component opens DevTools.

A browser inspector mapping a-button, m-post-card, o-post-feed, t-archive-layout, p-blog, and u-container to their likely folders

The prefix is a compact routing hint. It narrows both responsibility and file location before the developer searches the repository.

Before opening the editor, I can infer that a-button lives in atoms, m-post-card in molecules, o-post-feed in organisms, and t-archive-layout in templates. That shortens the distance between “What am I looking at?” and “Here's where I can find it.”

The search panel still exists in VS Code. A single-character prefix hasn't defeated the fuzzy search industry. But good naming lets search confirm an expectation instead of beginning an archaeological expedition.

Structure Resists Technical Debt

This structure doesn't prevent every bad decision. No naming convention has yet stopped a Friday-afternoon shortcut from developing a long and successful career.

It does give future developers a shared default. They can see how components are classified, what owns each style, when a piece deserves extraction, and where its code probably lives. That makes it easier to extend the existing system instead of introducing a second component philosophy, followed six months later by a third philosophy that uses nearly the same words differently.

Technical debt often grows in those seams: several reasonable approaches that don't complement one another. A visible hierarchy reduces that ambiguity. New code still requires judgment, but it begins from the same map.

Good Anatomy Keeps a Website Healthy

Atomic Design gives me a hierarchy. BEM gives it grammar. Prefixes turn it into a project map. Together, they give a website good anatomy: every part has a scale, an owner, a location, and a reason for being there.

The website won't literally breathe, demand lunch, or become disappointed by the post-launch retrospective. But with good anatomy, it can still behave like a healthy living system: growing without losing its shape, adapting without breaking its bones, and continuing to serve the purpose it was built for.

That's the goal. Not clever code, but a healthy system the next person can work in without first performing exploratory surgery.