One of the biggest mistakes developers make is treating components as UI elements instead of interfaces.
A Button is not just a button.
A Modal is not just a modal.
An Input is not just an input.
Every component you build becomes an API that other developers will use.
And just like any API, its design determines whether people use it correctly or spend their time fighting it.
Most components start simple:
<Button text="Save" />
A few months later they become:
<Button
text="Save"
loading
disabled
variant="primary"
icon={<Save />}
size="large"
fullWidth
rounded
/>
At this point, the component is trying to solve every possible problem.
What looks like flexibility is often a sign that the API design is losing clarity.
The best component APIs don't maximize options.
They minimize ambiguity.
For example, instead of allowing multiple flags that can conflict:
<Button
loading={true}
disabled={false}
/>
You can model the component as a set of valid states:
type ButtonState =
| "idle"
| "loading"
| "disabled";
Now usage becomes:
<Button state="loading" />
Cleaner.
More predictable.
Harder to misuse.
This idea extends far beyond React.
Good software design is not about handling every possible mistake.
It's about making mistakes difficult to make in the first place.
The strongest codebases are not the ones with the most features.
They're the ones where the architecture guides developers toward the correct decisions.
Because great components don't just render UI.
They communicate intent.
Top comments (0)