1. 04The UI kit: primitives, styles, and interactions
  2. 05Building components with clear responsibilities
  3. 06Shared abstractions versus local composition
  4. 07When a component library needs an escape hatch
  5. 08Third-party UI dependencies are infrastructure too

Component libraries and UI infrastructure

05

Building components with clear responsibilities

Shared components stay flexible when their APIs preserve composition, focused behavior, and documented system variants.

By Eyal Ellenbogen August 9, 2026 3 min read

Shared components break down when they try to anticipate every product scenario that might contain them. When a component library begins accepting isolated product requests, component APIs quickly bloat with boolean flags, internal conditionals multiply, and a generic building block turns into an unmaintainable feature framework.

A shared component is a focused contract. Its job is to make a stable design or interaction decision easy to use, while leaving product code responsible for the domain data and business logic it supplies.

A card owns its container, not its contents

A container component like a Card is the classic test of responsibility. A card's job is purely structural: it provides a border, elevation, padding, and the visual variants defined by the design system. It should have no idea whether it holds an account summary, an empty state, or an invoice table.

<Card appearance="outlined">
	<AccountSummary />
</Card>

The danger begins with shortcut props like isAccountCard. Soon after, another team requests hasFooterAction, followed by statusBadge and approvalState. Within a few releases, the card is full of internal if/else branches and bespoke prop configurations for specific workflows. Keeping the component's API focused on system variants (borders, elevation, padding) leaves consumers free to compose whatever content they need inside it.

Composition preserves flexibility

More complex components often need internal structure without prescribing the content inside. A callout or alert banner, for example, coordinates status colors, icon placement, and dismiss mechanics, but it should not dictate the message text or the exact buttons rendered.

Compound subcomponents and named slots let the library manage accessibility and layout while leaving content decisions to feature teams.

<Callout appearance="warning">
	<CalloutTitle>Payment overdue</CalloutTitle>
	<CalloutBody>Invoices past 30 days are subject to late fees.</CalloutBody>
	<CalloutActions>
		<Button>Pay now</Button>
	</CalloutActions>
</Callout>

This separation keeps the contract clean. The callout manages how elements are positioned and announced to assistive technology, without ever needing to inspect account balance models, verify user permissions, or fire the payment request itself.

High-level defaults still have a place

Not every component should require assembling multiple compound pieces by hand. A select dropdown involves a trigger, floating listbox, option semantics, keyboard navigation, and focus restoration. That composition is common enough, and tricky enough to build correctly, that a ready-made default is valuable.

The key is keeping that default focused on interaction mechanics rather than product features. A Select can manage selection states and keyboard navigation, but it should not fetch its own data or make assumptions about backend schemas. When a feature workflow diverges from that standard default, we can assemble the underlying primitives directly instead of forcing the shared control to absorb one-off configurations.