Black and White: A Design System Built for Focus
Why we build every product on a minimalist black and white design system: closed gray ramps, type-scale hierarchy, and accessibility by default.
By Vitor Lima
We build every OLUWA product in strict black and white. No brand blue, no accent teal, no "success green" creeping into a toast notification. A minimalist black and white design system is not nostalgia for the terminal and it is not a moodboard we fell for. It is a working constraint that makes our decisions faster, our interfaces more legible, and our accessibility baseline higher by default. This is why we chose monochrome, what we gave up, and the tokens and rules you could adopt tomorrow.
Why we removed color on purpose
Color is the most abused variable in interface design. It is cheap to add and expensive to govern. The moment you have a palette, every element competes for one of your hues, and "what color should this be" becomes a question you answer hundreds of times a week, usually inconsistently.
A destructive button is red. But so is an error state, and a required-field marker, and the notification badge, and the chart series for churned users. Red now means five things, which is to say it means nothing.
Removing color removes that entire class of decision. When you cannot reach for a hue to signal importance, you signal it with the tools that actually carry meaning: position, size, weight, and whitespace. Those tools are more honest. A heading reads as important because it is large and sits at the top with room around it, not because someone painted it purple.
The takeaway: if you are using color to fix a hierarchy problem, you have a hierarchy problem, not a color problem. Monochrome refuses to let you paper over it.
Hierarchy without color: three levers
With hue off the table, three levers do all the work.
Type scale. We run a modular scale with clear jumps, not a continuous gradient of sizes. Steps that sit too close read as noise; the eye needs unambiguous rank. Our working scale is roughly 12 / 14 / 16 / 20 / 28 / 40 / 64px. Body text sits at 16, secondary metadata at 14, and everything above 20 is structural. If two things need to feel different in importance, they get different steps, never the same step in two shades of gray.
Weight and case. With one typeface and no color, weight becomes a primary signal. We use three weights at most: regular, medium, and a single bold reserved for genuine emphasis. Uppercase with tracked-out letterspacing does the job of a "label color," marking a string as a category or eyebrow without a tint.
Space. Whitespace is the load-bearing wall of a monochrome layout. Proximity groups related things; distance separates them. In MadaiOps, our crypto orders app, the order ticket relies on this entirely: price, size, and side sit tightly grouped, while the confirm action is pushed away with deliberate space so it can never be fat-fingered next to an input. No red "danger" styling required. The spacing does the guarding.
Contrast is a system, not a vibe
The trap with black and white is thinking you only have two values. You have a full grayscale ramp, and the discipline is in restraining it. We define a small, fixed set of steps and forbid off-ramp grays. Here is the ramp we actually ship:
--ink-000: #000000 /* primary text, headings */
--ink-700: #3d3d3d /* body on light, secondary headings */
--ink-500: #6b6b6b /* metadata, captions, disabled-adjacent */
--ink-300: #b0b0b0 /* borders, dividers, placeholder */
--ink-100: #e6e6e6 /* subtle fills, hover backgrounds */
--paper: #ffffff /* base surface */
Six values. That is the whole system, plus its inverse for dark mode. The rule we enforce in review: no gray may exist that is not on the ramp. The instant someone eyedroppers a #8a8a8a because "the border felt a touch heavy," the system starts to rot. A closed set of values is what keeps a monochrome UI from drifting into muddy sameness.
Text clears 4.5:1, structure clears 3:1
Every text-on-surface pairing is checked against WCAG contrast ratios before it ships. --ink-500 on --paper lands around 5.7:1, comfortably past the 4.5:1 threshold for body text. --ink-300 fails as text and is therefore only allowed for borders and dividers, where the 3:1 non-text threshold applies. Encoding the rule into the token's permitted uses, not just its value, is what makes it stick.
The accessibility dividend
This is where monochrome quietly wins. Roughly one in twelve men has some form of color vision deficiency, and any interface that leans on red-versus-green to carry meaning is illegible to a meaningful slice of users. We never hit that wall, because we never encoded meaning in hue to begin with. Our states are distinguished by shape, label, weight, and position.
That discipline forces a healthier pattern everywhere: never let a single channel carry meaning alone. A disabled button in our system is not just lighter. It loses its border and its label drops to --ink-300, so the state survives on a grayscale screen, in bright sunlight, and for a color-blind user identically. Youp, our journaling app, marks mood entries with iconography and label text rather than the usual red-to-green sentiment gradient, so the emotional history reads without relying on a spectrum many people cannot see.
You get high-contrast and forced-colors support close to free, too, because you never fought the browser's own accessibility overrides with a delicate brand palette.
How constraint speeds decisions
The underrated benefit is velocity. A palette is a combinatorial explosion: every new component multiplies against every hue and every state. Monochrome collapses that. With no color decision to make, a component's design reduces to type step, weight, spacing, and which grayscale token for the border. That is a decision you can make in seconds and defend in review with a rule instead of a taste argument.
It compounds, too. Because the token set is tiny and closed, components compose predictably: a card, a table row, and a modal all draw from the same six grays and the same seven type steps, so they look related without any coordination. New surfaces inherit consistency instead of negotiating for it. For a small studio shipping several products, that shared substrate is the difference between four apps that feel like one family and four apps that feel like four vendors.
The tradeoff is real and worth naming. Monochrome has less raw expressive range, and some categories genuinely need color: data visualization with many series, anything mapping to a physical color, brand moments that trade on warmth. We do not pretend otherwise. Our stance is that color should be earned and rare, introduced deliberately for a specific job, never as the default lever for hierarchy. When we do reach for it, it lands hard precisely because everything around it is quiet.
Rules you can adopt tomorrow
If you want to try this without rebuilding everything:
- Close your gray ramp. Pick five or six values, name them, and ban everything off the ramp in code review. This single rule does most of the work.
- Make one type scale and use only its steps. No arbitrary font sizes. If you keep needing a new size, the scale is wrong; fix the scale, don't add a one-off.
- Encode permitted uses into tokens.
--ink-300is "borders only," not just a hex value. Document it where engineers will read it. - Audit every meaning-bearing signal for a second channel. If removing color would make a state ambiguous, add shape, label, or position until it doesn't.
- Treat whitespace as a component. Give spacing named tokens and defend them in review the way you defend colors elsewhere.
Closing thought
Working in black and white is not a limitation we tolerate; it is a lens that clarifies. Strip out the easiest variable and the structural ones, rhythm, proportion, contrast, space, have nowhere to hide. The interface either has a real hierarchy or it doesn't, and you find out immediately. That honesty is worth more to us than any palette.