Why a component library can make product delivery slower


Once a team has built the same form a few times over, a pattern shows up. The validation messages sit in different places, keyboard focus behaves differently and the spacing is never quite the same.
At that point, a design system sounds overdue. And a shared field, button and error pattern genuinely does remove repeated decisions.
The trouble starts when that useful fix grows into a component for every card, workflow and layout before anyone knows which parts are stable. New requirements turn into props, teams wait on library changes, and the system meant to speed things up becomes another approval step.
So the question isn’t whether reuse is good. It’s which decisions are repeated, costly and settled enough to encode. Here’s how I’d tell the difference.
When every pattern becomes a component
The appeal is clear: if the product has cards, tables, filters, settings panels, application views, onboarding flows, data summaries, empty states, badges, and modals, creating reusable versions of all of them appears to prevent future duplication. The first version often looks efficient:
Then a new screen needs something slightly different; a prop is added; then another. Then the component supports several layout modes, action placements, density settings, border treatments, and content slots. Eventually, the “reusable” component requires more time to understand than a focused piece of product UI would have taken to build. The component library has become a place where unresolved product variation is stored.
Abstraction arrived before repetition
A shared component is most useful when the team understands the thing it is sharing. That generally requires more than one example. If two forms need the same label, validation, error, help text, focus treatment, disabled behaviour, and accessibility semantics, a shared `Field` primitive can remove real repeated work. If two screens happen to put text inside a rounded rectangle, that does not automatically mean they need the same `Card` abstraction. The decisions worth sharing are the ones that are genuinely repeated, stable and expensive to get wrong. Strong candidates often include:
These are areas where inconsistency creates user confusion, accessibility failures, or repeated engineering effort. Product-specific compositions often should remain product-specific:
The first group is infrastructure for interaction; the second group is where the product keeps learning.
Making every component flexible
Once a library exists, teams often try to prevent future duplication by making each component highly configurable. That can lead to APIs like:
<Card variant="outlined" density="compact" headerAlignment="between" footerPosition="sticky" actionPlacement="top-right" responsiveMode="stack" elevation="medium" withDivider/>A configurable component can be useful; too much configurability moves design complexity into props. The user of the component now has to understand:
- Which combinations are valid
- Which variants are visually coherent
- Which behaviours remain accessible
- Which choices are approved
- Whether a new requirement belongs in the component or outside it
The library has not removed those decisions; it has only made them indirect. I prefer smaller primitives with clearer responsibilities, then composition at the product layer.
<Panel> <Stack gap="space-4"> <ApplicationStatus /> <NextAction /> <TaskList /> </Stack></Panel>This is often easier to evolve than a “universal dashboard card” that tries to anticipate every product screen.
Accessibility is a strong reason to share primitives
Some interaction problems are too expensive and risky to rebuild repeatedly. A dialog needs more than visual styling: it needs focus management, accessible naming, keyboard behaviour, escape handling, focus restoration, and a predictable relationship to the underlying page. The same is true for menus, selects, comboboxes, validation messages, toast notifications, and interactive tables. These are strong design-system candidates because a well-tested primitive prevents repeated accessibility regressions.
The system does not make the product automatically accessible, and it cannot decide whether a label is clear or whether a workflow is understandable. It can make fundamental interaction behaviour reliable enough that teams do not need to rediscover it on every feature.
Documentation should answer real delivery questions
A component library can also become slow because its documentation is detached from actual work. The useful documentation answers questions people face while building:
- When should I use a dialog rather than navigate to a page?
- Which button treatment is appropriate for destructive action?
- How should a field show asynchronous validation?
- What should happen when a table has no results?
- How does this component behave on keyboard navigation?
- Which loading, empty, error, and success states are required?
A catalogue that only shows every prop may be complete but still not help someone make a good product decision. The most useful documentation sits close to the decisions that otherwise get repeated inconsistently.
Measure whether the system is helping
A design system should be evaluated by outcomes:
- Do common screens take less time to build?
- Are engineers making fewer repeated styling decisions?
- Do accessibility regressions decrease?
- Can cross-product changes be made safely?
- Are design reviews focused more on workflow and hierarchy?
- Are components easier to use than recreating them?
- Does the system make the correct default easier than an incorrect one?
If the answers are no, adding more components is unlikely to fix the issue. The system may need to become smaller, more opinionated, better documented, or closer to the real work teams are doing.
Final thoughts
A component library is not automatically a design system. A useful design system is a set of shared, tested decisions that removes recurring work and protects important interaction behaviour. Start where inconsistency is costly, abstract only after repetition shows you what is stable, keep product-specific workflows close to the product and make the good path easier than the custom one.
The aim is a library that helps teams spend less time rebuilding solved problems and more time on the user’s actual problem, rather than one that can render every possible interface.