When a Design System Outgrows Its First Set of Rules
Design systems typically launch with a straightforward promise: one set of buttons, a unified typography scale, and consistent elements across every product surface. But as the product grows, so do the edge cases. A button that served the original feature set may no longer accommodate new specifications, and the restrictions baked into a component's code can make it faster to build something from scratch than to reuse what the library offers.
The question becomes how to evolve a system when the teams consuming it are no longer sitting next to its maintainers. As complexity mounts, the answer often lands somewhere on a spectrum between two strategies: keeping base properties accessible for direct modification, or developing an abstract shared vocabulary around component properties.
At Spotify, the design system Encore aims to give fellow Spotifiers as much autonomy as possible. Configuration is available in the components, but it is not always the first tool reach for — and understanding why requires looking at what each approach actually costs and delivers.
Defining the Abstraction Spectrum
Abstraction, in this frontend context, measures how far the code written by a developer sits from the HTML and CSS ultimately rendered in the browser. Low-level abstraction touches CSS or HTML elements directly. High-level abstraction works with custom properties that carry their own meaning and values, which in turn modify underlying CSS or elements within a component.
Two terms anchor the discussion:
- Customization — Custom styles added external to the component, referencing HTML elements and touching CSS properties directly. This is low abstraction.
- Configuration — Making the original component more flexible by passing additional parameters for varied behavior. This is high abstraction.
The Available Toolkit
Between those poles, several concrete strategies are common in React-based systems:
- Powerhouse definitions: Assigning a semantic definition to a collection of underlying properties. Enum props, for example, enable expressive configuration while remaining typesafe.
- Prepacked guidelines: Utility classes modify individual CSS properties in a granular way, referencing the design system's style guide without forcing developers to write CSS themselves.
- Property passthroughs: Passing children, className, and props through to the rendered elements so feature developers can inject custom styles and components into the system's building blocks.
- Direct overrides: The closest to raw CSS and JSX, overriding existing classes and properties for maximum control — at the cost of unchecked specificity.
Customization: Speed and Freedom vs. Fragmentation
Customization's primary appeal is autonomy. Feature developers can modify components to fit their needs without waiting for the design system's release cadence, which is valuable when deadlines are pressing. Untethering from system constraints can also unlock more innovative approaches to a problem.
The downsides follow quickly. A local override solves an immediate issue but often drifts from the system's broader standards. If the pattern spreads across a codebase, that local code is invisible to other developers — it must be duplicated rather than shared. And when the design system ships a sweeping visual update, overridden padding, spacing, or colors stay put, frozen against the evolution of the official component.
Configuration: Consistency and Reuse vs. Bottlenecks
Configuration pays off when emerging variations find their way back to the parent component. Reused variations can be tracked and kept consistent. When system-wide changes land, consumers can upgrade with confidence that their local overrides won't break. The model also encourages contribution — teams whose needs aren't covered must feed those variations back into the system to benefit from them.
But contribution is a two-sided coin. Relying on the system means code must be developed and released in a separate library before features can use it, introducing a dependency on another team and slowing feature work. Rigidity follows as well: fewer consumer options mean better consistency but tighter constraints on experimentation. Configurable systems also shift documentation burden to the maintainers — consumers must learn the abstract vocabulary defined for the components instead of relying on well-documented baseline HTML and CSS properties.
Choosing Between the Approaches
In practice, successful systems end up with a mix. Several factors point toward which end of the spectrum a given situation calls for:
- Feature maturity: Early in a feature's life, the design is still settling. Customization gives access to every property you might realize you need during iteration. Established components, conversely, come with existing use cases to mine, making it easier to build meaningful configuration options for everyone.
- Product maturity: A young product makes it hard to predict which conventions will stick. A first-time pattern might warrant customization; when the same pattern appears across other parts of the product, it is time to inventory those variations and consolidate them into configuration.
- Timeline: Customization gets something out the door fastest, but design system consumers can often find a middle ground — the closest approach to configuration that still meets the delivery deadline.
- Reusability: If a pattern clearly applies across features, configuration removes duplication risk and benefits teams beyond the one that raised the need.
Tradeoffs Are a Reflection of Values
Mature products and features make configuration more feasible and more beneficial. But the low-level, iterative nature of customization remains well-suited to prototyping and bespoke features still under active change.
There is no single correct strategy for evolving a design system. The balance between customizable flexibility and configurable consistency should mirror what the company values. At Spotify, where team autonomy is prized, the design system leans toward customization — not because a more configurable system couldn't be sustained, but because it is one tool among several, chosen deliberately per use case rather than applied as a universal answer.



