Skip to content

Separating Component Layers: From Primitives to Domain

When organizing component structures, it’s easy to fall into the trap of the "lazy" approach: throwing everything into a single ui/ folder.

2 min read
Component Library
Design System
React
Primitives
Frontend Architecture

When organizing component structures, it’s easy to fall into the trap of the "lazy" approach: throwing everything into a single ui/ folder. The problem is that this usually leads to a mess once the application actually starts growing.

I prefer splitting component layers into three levels: primitives, patterns, and domain. Deciding where a component belongs is an art; if you get it wrong, reusing those components in another project becomes a headache.

First are the primitives. These are the building blocks like Button, Input, or Checkbox. They don't know anything about your business logic. Their only job is aesthetics, accessibility, and basic interaction behavior.

Then you have patterns. These are how we arrange primitives to perform specific tasks, for example, a FormField that bundles a label, an input, and an error message. Patterns are the choreography between components, but they still don't carry any "business names."

Finally, there is the domain. This is where components start to have actual meaning within the product, like InvoiceStatusBadge or ProjectPicker. These components use the product's vocabulary; they actually know what an "invoice" or a "project" is.

A common mistake is mixing these three up. I often see components inside a design system package that suddenly start fetching data or have props like entity="invoice". Once a component starts calling a query hook or holding API logic, it has crossed into domain territory and is no longer just ui.

When should a component go into a shared UI package? Only if it is product-agnostic. This means if you were to build a completely different app tomorrow, that component would still work without needing a rename.

On the other hand, keep components within the application code if they reference business entities, check permissions, or follow a workflow specific to one feature. Extracting components too early often just becomes an unnecessary cost.

The patterns layer is also easily misused. Many people create a patterns/ folder that ends up becoming a "junk drawer" for half-baked components that have accidentally leaked in some business logic. Remember: patterns can manage accessibility, but they shouldn't know about your API error codes.

Maintaining these boundaries is exhausting at first, but staying disciplined early on is vital. The goal is to keep your design system lean so the whole system doesn't turn into an uncontrollable giant.

That's the shape of it for me.