Component APIs that age well
A component API should protect product intent, supported variation, accessibility, migration, and escape hatches without becoming rigid.
A component API ages well when it protects product intent without trapping the team in yesterday's design.
Most component problems are not visual at first. They are small API decisions that slowly become expensive: a prop named after a layout instead of a behavior, a boolean that grows into six combinations, a variant that only makes sense for one screen, a slot that lets every consumer rebuild the component from inside, or a component that looks reusable but cannot explain its own states.
The visual system can still look organized while the API is quietly rotting. Designers keep adding exceptions. Engineers keep passing combinations nobody expected. QA finds state bugs in only one product surface. The component still exists, but it no longer carries enough product judgment.
That is why component API design is a strong engineering signal. It sits between design systems, frontend architecture, accessibility, documentation, and product delivery. A person who can design a component API well is not only naming props. They are deciding how future product work should happen.
Name the product behavior the component should protect across screens.
Expose only the choices product teams should safely make.
Give unusual cases a contribution path instead of hiding arbitrary styling hooks.
Start with the product job
Before naming props, I want to name the product job.
A Button is not only a rectangle. It commits to action hierarchy, loading behavior, disabled explanation, focus visibility, size, density, icon placement, and accessibility. A Table is not only rows and columns. It commits to comparison, scanning, filtering, empty state, loading state, row actions, permissions, and sometimes bulk selection. A Drawer is not only a panel. It commits to focus, containment, mobile behavior, scroll, escape, and return path.
If the product job is vague, the API becomes a bucket for visual requests. If the product job is clear, the API can say no.
For example, a status chip might have the job: "show a record's operational state in a way that supports scanning and next action." That job suggests the API should be state-driven, not color-driven. Consumers should not pass arbitrary green or red. They should pass a known status, and the component should decide label, tone, icon, and accessibility text.
This protects the product. If every screen chooses its own color, the status language drifts. If the component owns the mapping, the system can change the mapping once and keep the product coherent.
Variants should be decisions, not adjectives
Variant names are where component APIs often become vague.
Names like fancy, subtle, special, alternate, custom, compact-ish, marketing, and new are warning signs. They describe mood, not product behavior. They do not tell the next engineer when to use the variant. They also make documentation weak because the examples cannot explain the decision.
Better variant names connect to use:
- primary for the dominant action in a decision set
- secondary for peer actions
- destructive for irreversible or risky actions
- compact for dense operational surfaces
- inline for low-emphasis actions inside text or table rows
- empty for a state that needs guidance without pretending content exists
- warning for recoverable risk before commitment
The names do not need to be perfect, but they should help a product team choose correctly.
The test is simple: can I write a usage rule in one sentence? If not, the variant probably needs a better name, a narrower scope, or no API at all.
Boolean props do not scale well
Boolean props feel easy. They are also where component APIs often start to fail.
A Button with disabled, loading, fullWidth, iconOnly, danger, compact, active, selected, elevated, and quiet can technically support many combinations. That does not mean the combinations make product sense. Some combinations may be invalid. Some may be inaccessible. Some may conflict visually. Some may encode variants that should be named.
I am not against boolean props. They are useful for true binary states. The problem is using booleans to build a hidden design language.
Questions I ask:
- Is this really binary?
- Can this combine with every other prop?
- Does this prop change behavior or only style?
- Would a named variant be clearer?
- Is this exposing an internal implementation detail?
- Is this state controlled by product data?
- Does this need documentation?
For example, loading is a reasonable boolean because the component can define a clear pending behavior. But danger may be better as a variant or tone because it changes hierarchy, copy expectations, and sometimes confirmation behavior. Compact may be reasonable when density is a supported system mode. Tiny may be suspicious if it only solves one screen.
The goal is not to avoid props. The goal is to keep the API from becoming a bag of switches.
Slots are power tools
Slots and render props are useful because they let a component adapt without knowing every future layout.
They are also dangerous. A slot can become a hole in the system where every consumer rebuilds the component's hierarchy. The component looks reused in imports, but the product experience is no longer governed by the component.
I want slots when the inserted content is a real extension point:
- table cell content
- card footer actions
- form field help content
- empty state action group
- drawer body content
- command item metadata
I am more cautious when the slot controls structure the component should own:
- replacing the accessible label
- bypassing the action hierarchy
- changing focus order
- altering the error association
- overriding layout density
- injecting arbitrary interactive controls into a component that owns keyboard behavior
Slots should come with usage examples and constraints. If a slot needs five paragraphs of warnings, the component may need a different design.
State belongs in the API
Components that ignore state force every consumer to rediscover behavior locally.
An Input should know about value, label, help, error, disabled, required, and maybe validation timing. A Table should know loading, empty, no results, error, partial data, and permission state. A Card that represents a record may need selected, pending, unavailable, or stale states.
State API design matters because states are product promises. A loading table says the system is working. An empty table says there is nothing here yet. A no-results table says the filters excluded everything. An error table says the product could not complete the request. Those are different messages.
If the component does not distinguish them, product teams will invent local copy and local layouts. The system becomes inconsistent precisely when users need clarity.
Guide setup, creation, import, or first useful action.
Preserve context and help the user adjust the query.
Explain recovery and avoid pretending the product state is known.
When I design a component API, I want states to be explicit enough that QA can enumerate them. If a state cannot be named, it probably cannot be tested or documented well.
Accessibility should be part of the contract
Accessibility cannot be left to the consumer when the component owns interaction.
If a component is a dialog, menu, combobox, tabs, disclosure, tooltip, or table, it owns semantic behavior. The API should make the accessible path hard to break.
That means:
- require a label or title when needed
- preserve focus behavior
- define keyboard rules
- associate errors with fields
- expose described-by relationships carefully
- prevent invalid nesting when possible
- document when an icon-only control needs visible or accessible text
- handle reduced motion where motion is built in
The component API should not allow a screen to accidentally ship without a name, without a focus path, or with a misleading role. Some of this is enforcement. Some of it is documentation. Some of it is review culture. All of it belongs to the API.
For candidate positioning, this matters because it shows that I think about component design as product infrastructure, not just visual reuse.
Escape hatches need names
Every design system needs escape hatches. The mistake is pretending it does not.
Products encounter unusual cases: a dense admin workflow, a campaign surface, a one-off integration warning, a legal requirement, a legacy migration path. If the system has no path for those cases, teams will create local styles anyway.
The API should distinguish between supported variation and escape:
- supported prop for common, documented choices
- composition for expected layout differences
- local wrapper for a contained product need
- contribution issue for a repeated pattern
- one-off override with a removal condition
The removal condition is important. If an override is temporary, say what makes it temporary. Maybe it disappears when the shared compact table ships. Maybe it disappears when the legacy billing flow is removed. Maybe it becomes a system contribution after two more screens need it.
Without that condition, escape hatches become architecture.
Documentation should explain judgment
Component docs often list props and examples. That is necessary, but not sufficient.
Good docs explain judgment:
- Use this variant when the action is the primary decision on the surface.
- Do not use compact density on marketing pages.
- Prefer the empty state action only when the user can create the first record immediately.
- Use warning tone for recoverable risk, destructive tone for irreversible action.
- If you need a new status, add it to the status map instead of passing custom color.
Documentation should also include failure examples. Show the wrong usage and explain why it is wrong. This makes the system more useful because teams often learn from near misses, not only ideal examples.
The docs should answer the questions a product team has under deadline. Which variant do I use? What if content is long? What if there is no data? What if the action is destructive? What if mobile gets tight? What if the status is pending? What if I need something not supported?
If the docs cannot answer those questions, the component API will be interpreted locally.
Component APIs and analytics
Some components carry analytics implications.
A command palette, checkout button, onboarding checklist, table filter, or AI review panel may produce events that product teams rely on. The component API should make those events consistent without hiding product meaning.
I am cautious about components that automatically log generic interactions. Button clicked is rarely enough. The product needs to know what decision happened. But components can help by standardizing event shape, source, and state.
For example, a table filter component might emit a normalized filter changed event with filter id, operator, result count range, and surface. A command palette might emit opened, query submitted, action selected, empty result, and escaped. The consumer still names the product context, but the component protects consistency.
This is where frontend architecture and product analytics meet. A component API can make measurement easier or make it noisy for years.
Migrations need API empathy
Changing a component API is a migration, even if no database is involved.
The more widely used the component, the more the migration needs care. A clean new API is not enough. The team needs a path from old usage to new usage.
I like migrations that include:
- old and new examples
- codemod if the change is mechanical
- deprecated prop warnings when useful
- screenshots of key surfaces
- list of routes checked
- documentation update
- support for both APIs during one release if risk is high
- final cleanup issue
Search every consumer and group by supported, risky, and invalid patterns.
Change low-risk usage first, preserve behavior, and test important surfaces.
Delete compatibility only after routes, docs, and examples prove the new contract.
This is another strong hiring signal. It shows I understand that design systems are used by teams, not just designed in isolation.
The case study angle
A component API case study should not only show the component gallery.
It should show:
- the product pressure that exposed the API weakness
- the old API shape and why it failed
- the new decision model
- state coverage
- accessibility contract
- migration plan
- routes or products moved over
- proof that local overrides decreased
That story is stronger than "I built a component library." It shows product reasoning, implementation care, documentation, and adoption.
For example, a table API case study could show how empty, no-results, loading, error, permissions, row actions, and density became explicit states. A button API case study could show how hierarchy, pending behavior, destructive action, icon-only labels, and mobile density became constraints. A status API case study could show how operational state moved from local color choice to a shared state map.
The visuals should be artifacts: API decision map, state matrix, migration checklist, before-and-after usage examples.
Review the consumer, not only the component
A component can look good in isolation and still fail in product use.
That is why I like reviewing a component API through real consumers. Pick three or four screens that represent different pressure:
- a dense admin screen
- a mobile commerce flow
- an empty first-run state
- a settings page with validation
- a table with permissions
- a destructive confirmation
Then ask whether the API still makes sense. Does the component protect the right behavior? Does it force local wrappers everywhere? Does it make long copy awkward? Does it support mobile without a special case? Does it make the accessible path clear? Does it keep analytics meaningful?
This review often exposes better API decisions than abstract design debate. A prop that feels elegant in Storybook may become confusing when used across three real workflows. A slot that feels flexible may create broken hierarchy in a destructive flow. A variant that looks unnecessary may become obvious when the admin screen needs density.
Real consumers keep the API honest. They also make the documentation better because examples come from product work instead of invented demos.
Versioning is a communication problem
Component APIs change because products change. The question is whether the change surprises the team.
Small teams do not always need formal semantic versioning for internal components, but they do need communication. A breaking prop change should not arrive as a quiet diff that forces every feature branch to debug layout. A visual behavior change in a shared Button should not sneak into checkout, billing, and onboarding without review.
For meaningful API changes, I want:
- a migration note
- screenshots of important consumers
- old and new usage examples
- a list of removed or renamed props
- known invalid combinations
- QA surfaces
- timeline for removing compatibility
This is not bureaucracy. It is respect for shared infrastructure.
The strongest design systems feel calm because change has a path. Product teams can adopt a better API without fearing that every update will turn into a scavenger hunt.
Examples should include edge cases
The examples that ship with a component often tell the team how seriously to take the API.
If the docs only show perfect content, perfect data, and perfect spacing, product teams will not know what to do with reality. I want examples for:
- long labels
- missing optional text
- loading
- empty
- no results
- permission denied
- destructive action
- disabled with reason
- icon-only button
- mobile density
- keyboard focus
- error recovery
These examples do more than document. They test the API. If an edge case needs a local override, the component contract may be incomplete. If the example looks awkward, the product may need a different pattern. If the state cannot be represented clearly, the state model may be wrong.
Edge-case examples are also useful for designers. They make the system feel real. Figma libraries often show the prettiest version of a component; production docs should show the component under pressure.
API shape reflects team values
A component API tells future teams what the system cares about.
If the API exposes arbitrary color, the system values local styling freedom. If it exposes status, the system values semantic consistency. If the API requires accessible labels, the system values inclusive interaction. If it supports empty, loading, and error states directly, the system values product clarity under real data conditions. If it documents migration paths, the system values adoption.
This is why API work should not be treated as a private engineering detail. The names, states, and constraints become a shared language between design and engineering.
For example, choosing tone instead of color is a product decision. It says the system owns visual mapping, while consumers own meaning. Choosing density instead of small is a product decision. It says compact layouts are supported in operational contexts, not arbitrary shrinking. Choosing status instead of label plus color is a product decision. It says state has rules.
Those choices shape how the product grows.
What I would reject
I would reject a component API that makes unsupported work easy.
Examples:
- customColor when the system has semantic tones
- isSpecial when the special case is not named
- className as the primary extension strategy for product teams
- renderEverything when the component needs to own semantics
- iconOnly without a required accessible label
- disabled without a way to explain why
- statusLabel and statusColor instead of status
- compact and tiny and dense all doing overlapping work
- destructive as a visual-only tone without confirmation guidance
None of these is always wrong in every codebase. The issue is what they encourage. A good API should make the right product behavior easier than the local workaround.
The interview story
Component API work is excellent interview material because it can go deep quickly.
I would explain it in three layers:
- Product pressure: teams were creating inconsistent table states or button variants.
- API decision: I moved from styling props to semantic states, named variants, and documented constraints.
- Proof: local overrides dropped, state coverage improved, migration completed across key routes, and the next product screen shipped faster.
That answer can open into accessibility, design-system adoption, TypeScript types, documentation, testing, migration, or product tradeoffs. It also shows that I can think at the boundary between design and engineering.
The strongest part is the proof. I do not want to say only that the API was cleaner. I want to show what got easier: fewer one-off styles, fewer missing states, faster implementation, better keyboard behavior, easier QA, or clearer analytics.
A component API review checklist
Before publishing or changing a shared component API, I would run this checklist:
- Is the product job clear?
- Are variants named by use, not mood?
- Are invalid combinations prevented or documented?
- Are states explicit?
- Are accessibility requirements enforced where possible?
- Are slots constrained enough to preserve semantics?
- Are escape hatches named and temporary when needed?
- Are examples based on real product consumers?
- Is the migration path clear?
- Does documentation explain when not to use the component?
- Does the API support measurement when the component owns meaningful interactions?
- Can QA enumerate the states?
This checklist is not meant to slow the team down. It is meant to avoid the expensive version of speed: the kind where every product screen ships quickly by inventing a slightly different system.
The downloadable resource
This topic deserves a downloadable checklist because component API decisions are easy to forget during sprint work.
The resource should be a one-page review template:
- component name
- product job
- stable behavior
- supported variation
- state inventory
- accessibility contract
- extension points
- invalid combinations
- migration plan
- documentation examples
- proof of adoption
That template would help designers and engineers review a component before it spreads. It would also make a portfolio case study more concrete because the artifact shows the decision process, not only the final component.
Types help, but they are not the whole contract
TypeScript can make a component API much safer. It can prevent impossible prop combinations, require labels, narrow variants, and make state-driven APIs easier to consume. I like types that encode product constraints instead of only describing implementation.
For example, an icon-only button type can require an accessible label. A table state type can separate empty, no results, loading, error, and ready. A destructive action type can require confirmation copy. A status chip type can accept known statuses instead of arbitrary colors.
But types are not the whole contract. They do not explain when to use the component. They do not prove the visual hierarchy works. They do not test keyboard behavior. They do not tell support what the state means. They do not stop a team from choosing the wrong product pattern while still satisfying the compiler.
The best component APIs combine types, documentation, examples, visual review, and browser testing. Types catch impossible use. Docs teach intended use. Examples show pressure cases. Browser checks prove interaction. Together, they make the component safer to use in real product work.
This is another good interview point because it avoids a shallow answer. I do not want to say "I made it type-safe" as if that solved the whole design-system problem. I want to say the type system protected the contract, and the documentation, examples, and migration work helped the team adopt it correctly.
That distinction matters because product teams do not experience types directly. They experience whether the component helps them make the right decision faster.
That is the practical standard I care about in real production product work systems.
The hiring signal
Component APIs are small enough to inspect and large enough to reveal judgment.
They show whether a person can think beyond the screen in front of them. They show whether they can encode design intent in code without making the system rigid. They show whether they understand accessibility, documentation, migration, and product states.
That is the kind of engineering signal I want more of on my site. A good component API is not only clean code. It is a product agreement that future work can trust.
Use this after reading.
Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.
Design System Contribution Pack
A contribution brief, drift diagnosis, escape-hatch rules, and component-docs template for product teams.
Design-to-Code Handoff Checklist
A handoff checklist for turning Figma screens into build-ready components, tokens, states, and responsive requirements.
Front-End State Recipes
Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.