Most designers optimize screens. I optimize the system behind them.
The difference isn't philosophical — it's structural. And it shows up immediately when something breaks.
What a screen is, and what it isn't
A screen is a snapshot. One state, one context, one moment. It shows the happy path at the ideal viewport with the right content length and a user who knows exactly what they're doing.
A system is the set of rules that generates every snapshot. It handles the edge cases, the error states, the empty states, the content that's twice as long as the designer expected, the user who's on a 4-year-old Android with 20% battery and a slow connection.
Designing a screen is easy. Designing the rules that make every screen correct under every condition — that's the actual problem.
The questions that precede any design
When I start a project, I don't open a design tool first. I ask four questions:
How many states does this component have? Default, hover, active, disabled, loading, error, empty, skeleton. A button without all its states isn't a button — it's an optimistic rendering of one. The states you skip in design are the ones that break in production.
How does this scale? To other brands, other languages, other viewports, other contexts you haven't been asked to design for yet. A component that only works for the use case you were given isn't a component — it's a one-off.
What breaks at the edges? The interesting design happens when the content is three times longer than expected, when the network fails mid-action, when the user is interrupted and comes back to an unknown state. Design the exception, not just the rule.
Who touches this after me? A system that only works when you're there to explain it isn't a system — it's a dependency. The test: can an engineer implement this without a call?
The engineering background
Three years writing C#/.NET and MongoDB before moving into design changed how I think about interfaces.
Code is opinionated about constraints. You can't fake that something works — either it compiles or it doesn't, either the query returns the right data or it doesn't. The gap between "it looks correct" and "it is correct" doesn't exist in a codebase. It does in a mockup.
Design has a dangerous tendency toward optimism. Showing ideal states in ideal conditions to stakeholders who don't know what they're not seeing.
The engineering background is a permanent calibration against that optimism. Every decision I make in design asks: what happens when this fails? Not as a pessimistic habit — as a structural requirement.
A real example: 15 brands, one system
At Vocento — Spain's largest media group — the challenge wasn't designing a product. It was designing a system that 15 products could run on without breaking.
Each brand has its own editorial context, its own subscription model, its own content density, its own audience. But they all share the same underlying infrastructure.
The constraint was that any design decision made for one brand had to work for all fifteen. That eliminates a class of decisions immediately — anything that depends on a specific content length, a specific brand voice, a specific price point.
What remains after that filter are decisions that are structurally correct. Those are the ones worth making.
The result was a design system with a single component architecture that each brand instantiates differently — not through overrides and exceptions, but through a token system that separates what changes (brand identity) from what doesn't (interaction behavior).
40% reduction in design-to-development handoff time. Not because we moved faster — because the system had fewer questions that needed answering.
What this looks like in code
A system isn't an abstraction. It's a set of concrete decisions encoded in a way that makes the wrong choice harder than the right one.
// A component without system thinking
function Card({ title, description, image, cta }) {
return (
<div className="card">
<img src={image} />
<h2>{title}</h2>
<p>{description}</p>
<button>{cta}</button>
</div>
);
}
// The same component with system thinking
interface CardProps {
title: string;
description?: string;
image?: { src: string; alt: string };
cta: {
label: string;
href: string;
variant: 'primary' | 'secondary';
};
state?: 'default' | 'loading' | 'error';
}
function Card({ title, description, image, cta, state = 'default' }: CardProps) {
if (state === 'loading') return <CardSkeleton />;
if (state === 'error') return <CardError />;
return (
<article className="card">
{image && (
<figure className="card__image">
<img src={image.src} alt={image.alt} loading="lazy" />
</figure>
)}
<div className="card__body">
<h2 className="card__title">{title}</h2>
{description && (
<p className="card__description">{description}</p>
)}
</div>
<footer className="card__footer">
<a href={cta.href} className={`btn btn--${cta.variant}`}>
{cta.label}
</a>
</footer>
</article>
);
}
The difference isn't complexity — it's explicitness. Every edge case is a decision, not an accident.
The practical checklist
Before designing any component:
- List every state: default, hover, active, disabled, loading, error, empty, skeleton
- Define what happens with content 3× longer than expected
- Define what happens with content missing entirely
- Define the mobile behavior — not as an afterthought
- Name the component as if you won't be there to explain it
- Write the component spec before opening any design tool
Not screens. Systems. The screens are a consequence of getting the system right.