When you’re building a product on a team, there’s a natural division of labor for the human-facing layer. A designer owns the visual system. A frontend engineer implements it. A product manager writes the microcopy — the button labels, the error messages, the empty states, the tooltips. A UX researcher tests whether any of it makes sense. Each person brings a different perspective, and the product benefits from the friction between them.
When you’re building alone, you are all of those people. And the engineering is the easy part. The writing is not.
Every word is yours
THE WHEEL has enough pages to make consistency a real problem. A chat interface, a notebook, a content library, a document uploader, an import system, a search page, an insights dashboard, a follow-up tracker, an onboarding flow, encryption setup, account management, billing, admin tools, mobile-optimized views. Each page has states — loading, empty, error, success, partial, edge case. Each state needs words.
What does the empty notebook look like before someone has written anything? What does the error message say when encryption setup fails? What does the tooltip on the privacy toggle actually communicate? What does the confirmation dialog say before you permanently delete your account and all your data? What does the app feel like in the ten seconds between signing up and understanding what you’re looking at?
On a team, these questions get distributed. The PM drafts the copy, the designer lays it out, someone else pushes back and says “that’s confusing” or “that tone doesn’t match the rest of the product.” The friction is productive. It catches the moments where the person writing the words is too close to the system to see it from the outside.
Building alone, there’s no friction. Every empty state message, every error string, every onboarding prompt comes from the same brain that built the feature. You know exactly how the system works, which makes you the worst possible person to explain it to someone who doesn’t. The curse of knowledge is real and it lives in your microcopy.
I’ve caught myself writing error messages like “K_session unwrap failed — check IndexedDB state.” That’s not a user-facing error message. That’s me talking to myself. But when you’re deep in the encryption implementation and something fails, the natural instinct is to describe the failure in the terms you’re thinking in. Translating “the browser couldn’t decrypt your encryption key from local storage” into something a human being would understand — and more importantly, something that tells them what to do next — requires a deliberate context switch that nobody forces you to make when you’re working alone.
So I started treating every piece of user-facing text as a design decision, not an afterthought. The empty state isn’t just a placeholder that says “No items.” It’s the first impression for that feature. The error message isn’t just a string that gets logged. It’s the moment where the product either helps someone recover or makes them feel lost. The confirmation dialog before account deletion isn’t just a checkbox — it’s the moment where the product demonstrates that it takes data seriously, which matters a lot when privacy is central to what you’re building.
That context switch — from engineer thinking about implementation to human thinking about experience — is one of the most expensive things about building alone. It’s also one of the most valuable, because it forces you to understand your own product from the outside in a way that’s easy to avoid when you have a team creating distance between the code and the user.
The design system decision
About six weeks into building, I made a choice that felt absurd at the time: I built a
design system. Not a loose collection of shared components. A formal system with a
component catalog, enforced by CI — as in, a pull request that contains a raw
<button> element gets automatically blocked from merging.
Every interactive element in the application uses a design system component.
Button with variants for primary, secondary, ghost, danger, outline.
Input with built-in label, error state, and help text. Select,
Checkbox, Switch, Textarea, Modal,
ConfirmDialog, Alert, LoadingSpinner,
EmptyState, Card, Badge,
BottomSheet, FAB, TabNavigation. Each one has
consistent styling, consistent behavior, consistent accessibility attributes.
Why would a solo founder, pre-launch, with zero users, invest the time to build and enforce a design system?
Because I’d lived through the alternative.
In twenty years of product work, I’ve seen what happens when a product grows without a design system. The first engineer uses one shade of blue. The second uses a slightly different shade. The third copies the first engineer’s component but modifies the padding. By the time you have twenty pages, you have twenty subtly different versions of a button, and the product feels like it was built by twenty people who never talked to each other — because it was.
The conventional startup wisdom is to move fast and clean up the UI later. But “later” has a cost that’s invisible until it arrives. Every page you build without a design system is a page you’ll eventually need to retrofit. Every component you copy-paste instead of abstracting is a component you’ll fix in twelve places instead of one. Every inconsistency you ship is an inconsistency your users feel, even if they can’t articulate it. The product just feels… off. Unreliable. Like different people built different parts of it.
Which, in my case, they did — different versions of me, at different times, thinking about different problems.
What consistency actually buys you
The design system paid for itself within weeks, in three ways I didn’t fully anticipate.
First, speed. Once the components existed, building new pages became dramatically
faster. A settings page isn’t a design exercise — it’s a composition.
Input for the text fields. Switch for the toggles.
Select for the dropdowns. Button for the actions.
Alert for the validation feedback. The page looks right by default because
every component already looks right. I stopped spending time on visual decisions and
started spending time on interaction decisions, which is where the actual product value
lives.
Second, quality under pressure. When you’re building fast — and building
alone means building fast, because there’s nobody else — quality degrades in
predictable ways. You skip the loading state. You forget the error state. You hardcode a
string instead of thinking about what it should actually say. The design system acts as
a forcing function against these shortcuts. The EmptyState
component exists, which means every list has an empty state, because the component is
right there and using it takes less time than not using it. The
LoadingSpinner exists with a fullPage variant, which means
every async operation has a loading indicator, because it’s one line of code. The
components encode the quality floor.
Third, the thing I mentioned earlier — consistency that users feel. THE WHEEL is a privacy product. Trust is the entire value proposition. And trust is built in the details. If the settings page looks polished but the upload page looks rough, the user’s subconscious registers that inconsistency, and it erodes confidence. If every page feels like the same product — same spacing, same type scale, same interaction patterns, same tone — the user’s subconscious registers that too. It says: this was built carefully. Someone thought about this.
For a product asking people to store their most private thinking, “someone thought about this” matters more than most founders realize.
CI enforcement: the part that sounds crazy
The part that makes people raise their eyebrows is the CI enforcement. A GitHub Actions
workflow scans every pull request for raw HTML elements —
<button>, <select>,
<input type="text">, <textarea> — and blocks
the merge if it finds any in changed files. Direct localStorage access is
blocked at the ESLint level — an error, not a warning — so you have to use
the safe storage wrapper that handles Safari private mode gracefully instead of
crashing. The HTML element checks are CI-level (not ESLint); the storage checks are
both. It’s layered enforcement: the linter catches it while you type, and CI
catches it before it merges.
Why would you block yourself from using basic HTML elements?
Because the person most likely to skip the design system is me. At 2 a.m., deep in a
feature, it’s faster to type
<button onClick={handleSubmit}>Save</button> than to import the
Button component and set the right variant. The shortcut is always faster
in the moment. The CI rule removes the option, which means the shortcut never happens,
which means the consistency never degrades.
It’s the same philosophy as the compliance controls — the subject of my next post — build the enforcement into the system so that doing it right is the path of least resistance, not the path of most discipline.
And there are legitimate exemptions. File inputs aren’t in the design system
— <input type="file"> gets a // DS_EXEMPT comment
explaining why. Custom accessibility implementations that need specific ARIA attributes
get exempted. The system isn’t rigid. It just requires you to be deliberate about
the exceptions.
The privacy UX problem
The hardest design problem in THE WHEEL isn’t any individual component. It’s making encryption invisible.
THE WHEEL’s encryption architecture is — and I’m being objective here — complex. It has multiple layers, meaningful constraints, and very little room for getting casual about security decisions. It is not a simple system.
None of that should be visible to a user.
When someone writes a note, they should experience “I wrote a note.” Not “I wrote a note, and a complicated series of security controls fired correctly in the background.” The encryption should be as invisible as HTTPS. You don’t think about TLS when you load a website. You shouldn’t think about client-side encryption when you write a note.
But there are moments where the encryption has to surface. Setting up your encryption PIN for the first time. Receiving recovery keys and understanding that losing them means losing access. Entering your PIN on a new device. These are moments where the user needs to understand, at least partially, what’s happening — because the decisions they make have real consequences. If you lose your PIN and your recovery keys, your data is genuinely unrecoverable. That’s the whole point. But it’s also a moment where the UX has to be clear enough that people understand the stakes without being so technical that they bounce.
Writing that onboarding flow — the exact words that explain “you are about to set up encryption for your account, and this PIN is the only way to access your data, and we cannot recover it for you” — was harder than building the encryption itself. The engineering has a right answer. The words have to find a balance between accuracy, clarity, and not scaring people away from a feature that exists to protect them.
Building alone means sitting with that tension personally. There’s no UX writer to hand it to. There’s no user research to fall back on. There’s just you, trying to imagine being someone who has never thought about encryption at all, reading these words for the first time, and deciding whether to trust this product with their thinking.
What I got wrong
I’m not going to pretend I nailed all of this. Building alone means nobody catches your blind spots until users do.
The first version of the note editor had a save flow that made perfect sense to me and was confusing to everyone I showed it to. I’d built a pre-save dialog that extracted metadata — title, summary, tags, insights — using AI before saving. In my mind, this was a feature. You write, the AI analyzes, you review the analysis, you save. Logical. Sequential.
In practice, people expected to click “save” and have the note be saved. The intermediate step felt like the app was asking for permission to do something they’d already told it to do. The mental model mismatch was obvious in hindsight and invisible to me during development, because I understood why the step existed.
The first version of the encryption setup used terminology that was precise and meaningless to non-engineers. “Key derivation function.” “Recovery key entropy.” Accurate descriptions of real things that communicated nothing to the person who needed to understand them.
These are the kinds of mistakes that a team catches early and a solo founder catches late. The design system ensures visual consistency. It doesn’t ensure conceptual clarity. That part still requires putting the product in front of people and watching them use it — which is part of why the beta matters.
The solo founder UI paradox
There’s a paradox in building alone that I keep coming back to. The same isolation that makes UI decisions harder also makes them more coherent.
On a team, every page is a negotiation. The designer wants more whitespace. The PM wants more information density. The engineer wants the simplest implementation. The compromise is often good, but it’s always a compromise — and over time, the accumulation of different compromises across different pages produces a product that feels slightly incoherent. Not bad. Just… assembled.
Building alone, every decision flows from the same sensibility. The spacing is consistent because one person chose it. The tone is consistent because one person wrote it. The interaction patterns are consistent because one person designed them. There’s no negotiation, which means there’s no compromise, which means there’s no accumulated incoherence.
The tradeoff is that one person’s blind spots are everywhere, unchecked. But the design system, the CI enforcement, the safe storage wrappers, the component catalog — these are all mechanisms for checking yourself. They’re the closest a solo founder gets to the friction that a team provides naturally.
Build the system that enforces consistency, and then trust the system when you’re too deep in the code to think about consistency yourself. That’s the whole trick. It’s the same principle as the compliance controls, the same principle as the encryption architecture: make the right thing the default thing, and the quality takes care of itself.
Or at least — it takes care of the visual consistency. The words still require you to stop being an engineer and start being a human, one empty state at a time.