Imagine you're building a product listing for a new website.
You need a search field, filters, product cards, a responsive grid, and a way to retrieve products from the CMS.
One approach is to build each piece yourself. Write the CSS for the cards. Implement the filter controls. Assemble the grid. Connect the data. Get the page working.
Then you move on to the product detail page.
It needs a related-products section, so you build another grid. Perhaps its cards are slightly different. A few weeks later, a category landing page needs much of the same functionality.
Before long, the codebase contains several versions of things that solve essentially the same problem.
The issue isn't necessarily that the developers wrote poor code. It's that each feature was approached as a new implementation rather than a composition of capabilities the project already had.
There is another way to think about frontend development: understand the levels of abstraction available to you, reuse the highest suitable one, and deliver functionality in complete vertical slices.
Every abstraction saves you from rebuilding something below it
At the lowest level, the browser already gives us HTML elements, CSS, JavaScript, and built-in interaction behavior.
We can work directly with those capabilities. We can write bespoke styles, implement custom controls, and arrange everything ourselves.
But we can also build—or adopt—abstractions that package recurring decisions.
A styling system gives us reusable tokens, typography, and layout conventions. A UI primitive handles difficult interaction behavior. A styled component combines behavior with a particular visual treatment. A section arranges components into something meaningful, such as a product grid. A page template composes sections into a repeatable page structure.
Each level potentially saves us from making the same lower-level decisions again.
Think of the available building blocks like this:
For complex accessibility, focus management, and keyboard handling at the behavioral level, adopting Headless UI Primitives (such as Radix, Base UI, or Floating UI) allows teams to own presentation without reinventing intricate state machines.
This isn't a rigid technical hierarchy. Styling and interaction primitives, for example, address different concerns and can be combined in different ways.
It's a way to recognize how much functionality and how many decisions have already been packaged for reuse.
The higher-level building block isn't automatically better. A ready-made template that doesn't fit your requirements may be harder to adapt than a new page assembled from existing sections.
The useful question is: What's the largest existing building block that fits this job without forcing the wrong structure?
Off the shelf doesn't have to mean a third-party library
When developers hear “reuse,” they often think of installing another dependency.
That is one option. We can use a mature UI primitive instead of writing all the keyboard and focus behavior for a combobox. We can use an established styling system rather than inventing every spacing and typography convention.
But a project's own codebase should increasingly become its most relevant source of reusable building blocks.
After the first product listing is finished, the project may already have a ProductCard, ProductGrid, filter controls, and a listing template.
Those are now off-the-shelf options for the next feature in that project.
This is where abstraction and vertical slicing meet.
A vertical slice turns building blocks into a working feature
A vertical slice is not simply a collection of finished UI components.
It is a complete piece of functionality that works through the parts of the system it needs.
For our product listing, that means more than rendering a convincing mockup. A visitor should be able to open the page, search for a product, apply a filter, and see results supplied by the actual product data source.
To deliver that slice, we might:
Reuse the site's existing styling foundations.
Adopt a proven combobox primitive for search.
Adapt an existing ProductCard.
Compose a ProductGrid and filtering section.
Connect the listing template to Payload CMS.
Verify the result with real content, interaction tests, and responsive layouts.
In How to Take Over a Frontend: A 4-Step Production Framework, I detail how to integrate vertically with the data layer while preserving visual parity, rather than stalling delivery on speculative component rewrites. When developers understand that unfinished work is part of a deliberate architectural sequence rather than an unassigned gap, they avoid the coordination drag explored in When Helping Creates More Work.
Some pieces come from the ecosystem. Some come from the project. Some must be built specifically for this feature.
The goal isn't to maximize reuse at any cost. It's to deliver one complete experience without unnecessarily recreating capabilities that already exist.
And once that experience works, something important has changed.
The project now has more to reuse than it did before.
The second feature should start higher up
Now imagine building the related-products section on a product detail page.
We don't need to begin with CSS rules or product-card markup.
We may not even need to begin at the component level.
If the ProductGrid already supports the layout we need, we can reuse the entire section and supply a different set of products.
The original vertical slice has given the next slice a higher starting point.
Then we build a category landing page.
If its requirements match the existing listing template, we may be able to reuse that template, configure its filters and product source, and concentrate on the parts that are genuinely different.
The progression looks like this:
First feature: Assemble primitives and components into a new section and template.
Second feature: Reuse the section; implement only the missing integration or variation.
Third feature: Reuse the template if it fits; configure rather than reconstruct.
That is the compounding effect of a well-structured frontend.
The amount of work doesn't necessarily decrease with every feature—some features introduce entirely new problems. But the team no longer has to solve every old problem again.
Reuse the highest suitable abstraction, not the highest available one
There is a trap in this approach.
Once a team has created a reusable section, it can become tempting to make every future requirement fit that section. Soon, its API contains dozens of options, special cases, and conditional layouts.
The abstraction that was supposed to save time becomes difficult to understand.
So the rule needs a qualification:
Start at the highest abstraction that genuinely fits. Move down only as far as necessary.
If the template fits, use it.
If the template doesn't fit but its sections do, compose a different page from those sections.
If a section doesn't fit but its components do, reuse the components.
If the components don't fit, perhaps their UI primitives or styling foundations still will.
And if the requirement really is unique, build what it needs.
Balancing these layers objectively requires aligning technical tools with client business requirements rather than dogmatic tool bias, as discussed in Client Judgment vs. Preferred Technology.
This is also why not every piece of code needs to become reusable the moment it's written. Sometimes the right choice is to implement a feature clearly, then extract a shared abstraction when a second real use case reveals what the common contract should be.
Reuse should emerge from genuine shared requirements, not from trying to predict every future possibility.
A growing frontend should become easier to compose
The promise of vertical slicing is often framed around delivery: ship one working piece, validate it, then move to the next.
That's valuable, but there is a second benefit.
Each completed slice can expand the set of reliable abstractions available to the project.
The first feature may require substantial work at the component and section levels. The next may reuse an entire section. Another may be mostly template configuration and data integration.
Developers spend less time reconstructing familiar interface patterns and more time solving the requirements that make each feature different.
That's the frontend I want to build: not one where everything is forced into a universal component system, and not one where every new page starts with an empty file.
Then, for each new feature, we start as high up that ladder as the requirements allow—and deliver a complete vertical slice from there.
Related Reading & Frontend Architecture
For deeper guides on frontend design systems, UI behavior primitives, and delivery frameworks: