Rethinking Design Systems (opens in new tab)
Toss Design System (TDS) argues that as organizations scale, design systems often become a source of friction rather than efficiency, leading teams to bypass them through "forking" or "detaching" components. To prevent this, TDS treats the design system as a product that must adapt to user demand rather than a set of rigid constraints to be enforced. By shifting from a philosophy of control to one of flexible expansion, they ensure that the system remains a helpful tool rather than an obstacle.
The Limits of Control and System Fragmentation
- When a design system is too rigid, product teams often fork packages to make minor adjustments, which breaks the link to central updates and creates UI inconsistencies.
- Treating "system bypasses" as user errors is ineffective; instead, they should be viewed as unmet needs in the system's "supply."
- The goal of a modern design system should be to reduce the reason to bypass the system by providing natural extension points.
Comparing Flat and Compound API Patterns
- Flat Pattern: These components hide internal structures and use props to manage variations (e.g.,
title,description). While easy to use, they suffer from "prop bloat" as more edge cases are added, making long-term maintenance difficult. - Compound Pattern: This approach provides sub-components (e.g.,
Card.Header,Card.Body) for the user to assemble manually. This offers high flexibility for unexpected layouts but increases the learning curve and the amount of boilerplate code required.
The Hybrid API Strategy
- TDS employs a hybrid approach, offering both Flat APIs for common, simple use cases and Compound APIs for complex, customized needs.
- Developers can choose a
FlatCardfor speed or aCompound Cardwhen they need to inject custom elements like badges or unique button placements. - To avoid the burden of maintaining two separate codebases, TDS uses a "primitive" layer where the Flat API is simply a pre-assembled version of the Compound components.
Design systems should function as guardrails that guide developers toward consistency, rather than fences that stop them from solving product-specific problems. By providing flexible architecture that supports exceptions, a system can maintain its relevance and ensure that teams stay within the ecosystem even as their requirements evolve.