Skip to content
Case Studies

Design system adoption across multiple applications

  • React
  • TypeScript
  • Storybook
one component API that TypeScript can check
Plain props
with two other developers from the start
Team-built

Context

We built this library together from the beginning: me and two other developers. Since becoming Lead Frontend Engineer, I lead it.

I built components and reviewed API designs. I also spent time with the teams using them, working through requests and helping contributors understand why an API had the shape it did.

Challenge

A team is trying to ship a feature. The shared component gets them most of the way there, but the design calls for behavior it doesn’t support. Building around it gets the ticket moving. Bringing the request back to the library takes a conversation, and someone has to make that conversation worth having.

There was a cost on our side too. Once other teams depend on an API, changing it means asking them to change their code. A small addition can leave us with something we have to support for years.

Approach

Make the API predictable

One decision I pushed for was using regular props rather than compound components. Composition would give consumers more freedom to assemble the interface. For this system, I cared more about making the same component work the same way wherever a team used it.

That meant accepting a narrower API. A consumer could choose from the options we exposed, with TypeScript checking the props, instead of assembling the component’s parts themselves. There was less room to customize the structure, but also less for each engineer to figure out before using it.

I wasn’t arguing that composition was wrong. Some components need that freedom. Here, the tradeoff was between flexibility at each call site and decisions we could make once in the library. Consistent property names and predictable behavior mattered more than giving every consumer another way to build the same thing.

That choice also put responsibility back on us. If the API was deliberately limited, we needed to understand when a request exposed a real gap. A narrow API is only useful while it covers the work people need to do.

Work through the gaps with consumers

Conceptual example of the boundary: behavior needed by one product stays in that product. Adding it to the shared system gives the whole team something else to maintain.

I used working groups to make room for those conversations. Engineers could bring a request, question an API decision, or explain where the system was getting in their way. I stayed available outside the meetings too. Sometimes a call was enough to work through the reasoning.

When a team needed more from a component, we went through the specification together. Then we either delegated the implementation or made the change within our team. I reviewed APIs before they merged and worked with design on how the addition fitted the system.

The question I kept asking was whether the behavior belonged in a shared component. If it only served one product, keeping it there spared the library another API to support. A request could make sense for the product and still be the wrong addition to the system.

I saw the library as a quality multiplier. The standard in a shared component spreads into everything built with it. That made the review worth spending time on, including explaining the decision to the person doing the work.

Outcome

The library is now used across many of the company’s applications. Internal component and adoption counts are not reproduced here.

Reflection

I learned to slow down when a request felt urgent. Sometimes we already had something that covered it. Sometimes I needed to listen longer to understand why that component wasn’t working for the team. Going into the conversation ready to defend the library made the second case harder to hear.