🛠️DevKitPack
Back to blog
Design Systems5 min read

Designing Component APIs Your Team Will Love

A component's props are its public contract. Get them right and the component disappears into easy reuse; get them wrong and every feature becomes a fight with a boolean you regret.

When we talk about a component, we usually talk about how it looks. But the part a team lives with every day is its API — the props it accepts, the events it emits, the way it composes with everything around it. A beautiful component with an awkward API gets worked around, forked, and resented. A plain one with a thoughtful API gets reused happily for years. The interface is the product.

A component's API is its real contract

Every prop you expose is a promise. It says "this is a supported way to use me, and it will keep working." That promise is expensive: once a prop is in use across a codebase, removing or changing it breaks callers, so APIs accumulate weight far faster than they shed it. The implication is simple but easy to forget — be generous with internal flexibility and stingy with the public surface.

The best time to design the API is before the component exists, by asking how you wish it could be called. Write the ideal usage first, in the markup, and let that drive the implementation. An API designed from the caller's side tends to read well at the call site; one reverse-engineered from the implementation tends to leak its internals into every usage.

Props are a language — design them

Props are the vocabulary a developer uses to describe what they want. Good vocabulary is small, expressive, and hard to misuse. The classic failure is the boolean that breeds: isPrimary, then isSecondary, then isPrimaryLarge, until illegal combinations outnumber legal ones and nobody can tell which flags may coexist. A single variant prop with named values says more, allows less nonsense, and reads better.

// Fragile: booleans that can contradict each other
<Button primary large disabled outline />

// Clear: a closed set of meaningful choices
<Button variant="primary" size="lg" disabled />

The second version is not merely tidier. It makes the illegal states unrepresentable: a button cannot be both primary and outline at once, because variant holds a single value. Designing props so that wrong usage is hard to express is one of the highest-leverage moves in component design.

Sensible defaults

Most usages of a component are ordinary, and the API should make the ordinary case effortless. If a developer must specify five props to render the most common variant, the defaults are wrong. The goal is that the component does the sensible thing with no configuration, and that the props exist to depart from the default, not to reach it.

  • The zero-config render should be the most common, correct usage.
  • Defaults should be safe — never default to a destructive or surprising behavior.
  • Every prop should have an obvious default, so callers set only what differs.
  • If a prop has no reasonable default, question whether the component is doing too much.

Good defaults are a form of accumulated judgment. They encode the team's answer to "what is usually right here," so each caller does not have to rediscover it. The payoff is call sites that stay short and reveal only what is genuinely unusual about each particular use.

Composition over configuration

There is a fork every component author reaches: when a new need arrives, do you add a prop, or do you let the caller compose? Configuration — adding props — is tempting because it is quick, but it is how components bloat into thousand-line monsters with a prop for every contingency. Composition keeps the component small by letting callers assemble behavior from smaller pieces.

A Card that accepts header, footer, media, and badge props will eventually accept twenty more. A Card that simply renders its children, alongside a CardHeader and CardFooter the caller can arrange freely, never needs another prop for layout. Composition pushes flexibility to the call site, where it belongs, and keeps the component itself focused on one job.

Every prop is a promise you must keep. Add them slowly, remove them rarely, and let composition absorb the rest.

Controlled and uncontrolled

Interactive components carry state — an input's value, a dropdown's open flag — and the API must decide who owns it. An uncontrolled component owns its own state and is effortless for the simple case. A controlled component hands ownership to the caller, which is essential when the parent needs to drive or react to that state. The mistake is supporting only one.

The well-worn pattern is to support both: accept an optional value (controlled) and fall back to internal state when it is absent (uncontrolled), with a defaultValue for the uncontrolled starting point. It is a little more work inside the component, but it means the same component serves both the trivial form field and the tightly-orchestrated one, instead of forcing a fork.

Naming and consistency

Across a system, consistency is worth more than any individual clever name. If one component calls it onChange and another onValueChange and a third onUpdate, developers must look up each one, and the system feels like a collection of strangers. Pick conventions — for event handlers, for size and variant scales, for boolean prefixes — and apply them everywhere, even where a one-off name might read marginally better.

Consistency is what lets knowledge transfer. Once a developer learns that your components take a size prop with sm, md, lg, they can guess the next component's API correctly without reading the docs. That predictability is the quiet luxury a mature design system provides, and it is built entirely from boring, disciplined naming.

Designing yours

Start from the call site: write the usage you wish you could type, then build toward it. Prefer a closed variant prop over a swarm of booleans, give the common case great defaults, reach for composition before configuration, and name everything the way the rest of the system already does. Do this and your components stop being things people tolerate and become things they reach for first.

Related posts