The best abstraction isn't the one that removes the most code.
It's the one that removes the most decisions.
Repeated decisions are more expensive than repeated code
I used to judge abstractions by how much code they deleted. If I collapsed five copies of a button into one shared component, that felt like a win.
But duplicated code was never really the expensive part. Duplicated code is a symptom — it's what you see when the same decision has been made five times by three people.
The real cost is in the decisions:
- Should this control be a
<button>or a<div>with a click handler? - Does this variant exist in dark mode? At 2x zoom? For keyboard users?
- Is this the accent color, or just a color that looks like the accent color?
- Who decides when a pattern is official versus a one-off?
Every one of those is a judgment call someone has to re-litigate. Code is cheap to copy. Decisions are expensive to rediscover, and dangerous to discover inconsistently.
What a good abstraction actually encodes
A well-built design-system component doesn't just hide markup. It encodes a bundle of settled decisions so nobody downstream has to make them again:
The person calling <Button> makes one decision — primary — and inherits accessibility, interaction, theming, and consistency decisions that were made deliberately, once, by someone who thought about them.
That's what I mean by encoding decisions. The abstraction is the record of the argument, not just the output of it.
Constraints without removing judgment
There's a failure mode on the other side: the abstraction that tries to make every decision for everyone.
Over-configurable components are a smell I look for in every design system I audit. If a component exposes forty props, it hasn't removed decisions — it has redistributed them, plus added the decision of which prop combination to use. Every consumer is now configuring instead of composing, and every unusual configuration is a layout the designers never drew.
The abstractions that hold up create useful constraints while leaving product judgment alone:
- The system decides how a confirmation looks and behaves. The product decides when to confirm.
- The system decides what the destructive variant does to color and semantics. The product decides whether this action is destructive.
- The system decides how empty states are structured. The product writes the words.
That split is the whole game. Encoded decisions are the ones with a right answer; the ones left open are the ones that actually need a human in the room.
Why this matters more now
Code generation is getting cheap. Writing a plausible-looking component is approaching zero cost, and AI-assisted workflows make it trivial to produce five implementations of anything before lunch.
What doesn't get cheaper is deciding. Which of the five is right? What did "right" mean? Will it still be right in six months, in dark mode, in the second product?
When generation is cheap, the scarce resource shifts entirely to settled decisions. A system that encodes them — tokens, tested components, documented intent — becomes the thing that lets you accept generated code without multiplying your review surface. The generated code is only safe to keep if it lands inside constraints that were decided once, on purpose.
I've written before about design tokens as a contract — this is the same idea one level up. Tokens encode values; components encode behavior; the system encodes judgment.
The test I use now
When I evaluate an abstraction — mine or someone else's — I've stopped asking "how much code does this save?"
I ask: if a new engineer joined tomorrow and used only this abstraction, which decisions would they never have to think about?
If the answer is "not many," it's a code-sharing utility, not an abstraction. If the answer includes accessibility, theming, interaction states, and consistency — and it still leaves the product room to be the product — that's the kind that compounds.
The measure of a good abstraction isn't what it deletes.
It's what nobody ever has to decide again.
