Here's a scene that plays out constantly in startup codebases. A developer needs to pass a single value — say, an active state flag — from a parent component down to a leaf. The design system's component API doesn't expose a prop for it. The docs say to use the provided context. So now there's a context provider wrapping a section of the tree, a custom hook to consume it, and a new abstraction layer that will need to be maintained forever. For one boolean.
The value didn't need context. The API forced it there.
This is the version of the prop drilling debate that doesn't get enough attention. Most discussions frame it as a developer choice — you can use props, or you can use context, pick the right tool. But design system APIs make that choice for you, often badly, by deciding at the component level which data flows through props and which flows through context. When that decision is wrong, every consumer pays the cost.
Props Are Explicit. Context Is a Contract You Can't See.
The clearest articulation of why this matters comes from how Svelte's community frames the tradeoff: with props, the data relationship between parent and child is explicit on both sides. With context, you only see one side at a time. In the parent, you can't tell how the data is used downstream. In the child, you can't tell where it came from. Mistype a prop and you get an immediate dev warning. Misuse context and you get an undefined value producing weird runtime behavior that's hard to track down.
That's a debugging problem. But in a design system, it's also a comprehension problem. When a component's behavior depends on ambient context rather than explicit props, the component becomes harder to reason about in isolation. You can't look at a usage site and understand what the component will do — you have to trace the provider tree. For a mid-level developer onboarding to a codebase, that's a significant tax.
JSManifest's analysis of React Context in 2026 draws the distinction cleanly: Context is a dependency injection mechanism, not a state manager. It works well for values that change rarely — theme, auth session, locale. It quietly destroys performance when used for frequently changing state, because a provider value change triggers re-renders in every consuming component, even those that don't use the changed field. The failure mode is subtle enough that teams ship slow apps without understanding why.
Design system APIs that route component state through context — active tab, open/closed, selected item — are making a bet that this state changes rarely enough that the re-render cost doesn't matter. Sometimes that's right. Often it's not.
The Compound Component Pattern Is Where This Gets Complicated
Compound components — the <Tabs> / <Tab> / <TabPanel> pattern — are the canonical case where context makes sense. The child components need to coordinate without the consumer having to wire them together manually. Requiring explicit props for every piece of shared state would make the API unusable.
But there's a meaningful difference between using context for coordination between tightly coupled children and using context for configuration that a prop would handle fine. The first is a legitimate architectural choice. The second is over-engineering that creates abstraction debt.
The tell is whether the context is consumed outside the component family. If your <Card> component uses a context that only <Card.Header> and <Card.Body> consume, that's internal coordination — fine. If your <Button> variant depends on a ButtonGroupContext that has to be set up by the consumer, you've pushed complexity outward. The consumer now owns an abstraction they didn't ask for.
I wrote about a related failure mode in the wrapper trap post: composition patterns that add indirection without adding value. Unnecessary context providers are the same failure in a different shape. The abstraction exists to serve the system's internal architecture, not the developer using it.
The Signal to Watch For
The practical question when designing or auditing a component API: does this context exist because the alternative is genuinely worse, or because it was easier to implement?
If removing the context provider would require the consumer to pass three or more props through intermediate components that don't use them, context is probably the right call. That's the case the Svelte discussion identifies as legitimate — deeply nested consumers tied to data several levels up, where prop threading would couple every intermediate component to data it doesn't care about.
If removing the context provider would just mean adding one explicit prop to the component's public API, the context is adding complexity without solving a real problem. Expose the prop.
The design system's job is to make the right thing easy. Forcing context on consumers who would have been fine with a prop is the opposite of that — it's making the simple case harder in service of an abstraction that only benefits the system's internals. Audit your component APIs for this pattern before your consumers do it for you, in a Slack message at 11pm asking why their tree is re-rendering on every keystroke.
