How to build an app
like LEGO.
This is not a slideshow. You will press buttons, drag atoms, break rules, and get caught by a robot. Six chapters. Five playable labs. Four quizzes. One law.
This page is fine.
Now let 14 freelancers loose on it.
EVERYONE STYLES THINGS THEIR OWN WAY. WHAT COULD GO WRONG?
Nobody was wrong.
Everybody was different.
That's the disease.
The fix isn't "hire better freelancers." The fix is LEGO's move: nobody gets to invent their own brick. One factory, fixed sizes, pieces that snap only one way — atoms → molecules → organisms → pages. It's called atomic design, and each level gets a lab today.
ONE LAW: EACH LAYER SNAPS ONLY ONTO THE LAYER BELOW IT. LET'S GO MAKE PLASTIC →
Tokens.
Smaller than an atom: the plastic itself. Every color, size and font — decided once, given a name, locked forever. A component never says #1daf9c; it asks for --oi-bg-brand and the system answers. Why does that matter? Next slide. You'll press the buttons.
3,491 TOKENS IN OUR SYSTEM · EVERY PIXEL HAS A PASSPORT
Repaint the whole app.
Touch nothing but one token.
COMPONENTS EDITED: 0 · LINES OF CSS WRITTEN: 0 · THAT'S THE POINT
Drag one slider.
Every gap obeys.
NO ELEMENT OWNS ITS OWN MARGIN — THE WRAPPER OWNS EVERY GAP. THAT'S WHY THEY ALL MOVE TOGETHER.
Which one is a token?
Atoms.
The smallest piece you'd hand to someone: a button, a badge, an input. Each one is nothing but a recipe of tokens — and you're about to cook one yourself.
NEXT LAB: YOU BUILD OUR BUTTON, TOKEN BY TOKEN →
This sad grey thing wants to be
an OIButton. Feed it tokens.
MAGIC NUMBERS USED: 0 · RAW HEX ALLOWED: NEVER
Four heights exist.
Try to invent a fifth.
md (32px) IS THE DEFAULT — RIGHT ANSWER ALMOST EVERYWHERE
What is an atom
actually made of?
Molecules.
H + H + O = water. Input + button = search bar. Atoms snapped together suddenly do things. And this time you're doing the snapping — with your hands.
NEXT LAB: DRAG THREE ATOMS INTO THE ASSEMBLY ZONE →
Drag the atoms
into the assembly zone.
THE MOLECULE ADDS NO COLORS AND NO FONTS — ONLY THE ARRANGEMENT
Restyle the molecule.
Notice what you can't touch.
MOLECULES OWN THE GAPS BETWEEN ATOMS. THE ATOMS THEMSELVES? UNTOUCHABLE.
Touch an atom.
See who inherits the fix.
THIS IS THE REAL import GRAPH · HOVER OIButton — A FIX THERE HEALS EVERYTHING DOWNSTREAM
The avatar looks wrong inside
UserCard. Where do you fix it?
A template is slots.
A page is you, clicking them.
SLOTS FILLED: 0 / 4 · CSS WRITTEN BY YOU: 0 LINES
Congratulations — that page
contains zero CSS.
export default () => (
<DashboardTemplate>
<OIPageHeader title="Accelerators" />
<OIKpiStrip items={kpis} />
<OIChartCard series={gpu7d} />
<OITable rows={workloads} />
</DashboardTemplate>
); // no colors. no margins. no hex. just snapping.
Every visual decision happened four layers down, in labs you already ran. This is why a backend engineer's page looks exactly like the design lead's page: neither of them designed it.
Spot the rule-breaker.
You've been the builder.
Now command the robot.
Everything you just did by hand — picking tokens, snapping atoms, filling slots — an AI can do from a sentence. Because the bricks are strict, it can't mess up. We gave the whole company this robot: engineers, PMs, everyone. It's why nobody at OI waits for a designer anymore.
FINAL LAB: TYPE A PAGE INTO EXISTENCE →
Order a page.
Any page.
THE ROBOT CAN ONLY SNAP REAL BRICKS — WHICH IS EXACTLY WHY IT CAN'T FAIL
The robot isn't the magic.
The bricks are.
It can't hardcode a color — you saw the chip bounce off. It can't invent a 36px button — you tried. It can't do comic sans — you asked. Every guardrail you hit today is why AI + this system builds like a designer: bad design is structurally impossible. That's atomic design.
TWO DOORS AT OI: /prototype IN CLAUDE CODE (REAL CODE OUT) · CLAUDE DESIGN IN A BROWSER (ZERO SETUP)
Tokens → atoms → molecules
→ pages → anyone.
Decide everything once, at the smallest size. Then only ever snap.
Taught by Mehrshad · Design Lead — Open Innovation AI.
HOMEWORK: BUILD ONE PROTOTYPE THIS WEEK · ← HOME RESTARTS THE CLASS
The UX files.
20 real fixes, playable.
Atomic design in the wild: twenty documented UX problems from OICM+ — each rebuilt broken so you can feel it, then flipped to what shipped. Flip every 😖/😌.
DISAGREEING PAGERS · THE DECAPITATED TITLE · THE 83-FILE DARK-MODE COLLAPSE · AND 17 MORE →
Tables &
filters.
An MLOps product is 80% tables. Ours had 37 of them — and no two filtered, paginated, or footered the same way. The single biggest cluster of user pain. Wave W74 — "The Table Reckoning" — fixed it bottom-up.
4 PROBLEMS · ALL PLAYABLE →
Which "next page" is the real one?
TWO PAGERS RENDERED AT ONCE. TRY THEM — THEY DON'T EVEN AGREE.
ONE PAGER + A RANGE LABEL. pagination IS NOW A DISCRIMINATED UNION — YOU CAN'T RENDER TWO.
Every table filtered
its own special way.
SEARCH-ONLY · CHIPS+FUNNEL · DRAWER+SEGMENTED… ×11 VARIANTS. LEARN EACH PAGE FROM SCRATCH.
ONE OITableFilterBar: searchSlot + filterChips + actionsSlot. LEARN IT ONCE, USE IT EVERYWHERE.
"How much data is there?"
The table wouldn't say.
9 VARIANTS: RANGE-ONLY, SIZE-ONLY, REFRESH-ONLY, NOTHING… THE "SHOWING 1–25 OF 478" LABEL EXISTED ONLY IN STORYBOOK.
ONE OITableFooter: RANGE + PAGE SIZE + PAGER. THE ANSWER TO "HOW MUCH DATA?" IS ALWAYS IN THE SAME PLACE.
Demos promised a rich table.
Production shipped a skeleton.
THE PRODUCTION TABLE: 45 LINES. NO TITLE. NO TOOLBAR. NO EMPTY STATE. NO ERROR STATE. NO SELECTION. EVERY PAGE HAND-ROLLED ITS OWN.
TITLE · COUNT · TOOLBAR · SELECTION FOOTER · EMPTY & ERROR STATES — AS PROPS, NOT PER-PAGE REINVENTION.
The space
wars.
Five waves of civil war over who owns the gaps — W11 → W16 → W17 → W19 → W55. It ended with one primitive (the Brick Wrapper) and one law: nobody owns their own margins. These are the battles.
4 PROBLEMS · INCLUDING "WHERE THE F*** IS THE FILTER BAR" — A REAL QUOTE →
"Where the f*** is
the filter bar?"
CANVAS PADDING WENT TO 0 IN W16. CHROMED BRICKS SAT FLUSH ON THE EDGE AND DISSOLVED INTO THE BACKGROUND.
W55: OIBrickWrapper + OIBrickRow — EVERY BRICK CARRIES ITS OWN 12px INSET; ROWS ADD 12px = 24px CANVAS GUTTER, BY CONSTRUCTION.
A monitoring dashboard
that wasted a third of your monitor.
DEAD
DEAD
max-width:1600px HARDCODED IN THE TEMPLATE. ON A WIDE MONITOR: UP TO 360px OF EMPTY SPACE, EACH SIDE, ON A DATA-DENSE PAGE.
RULE 35: TEMPLATES OWN ZERO LAYOUT DECISIONS. FULL WIDTH BY DEFAULT; PAGES OPT IN TO NARROW.
Two "you are here" signs.
Pointing from different maps.
SHELL RENDERED ONE TRAIL, TEMPLATE RENDERED ANOTHER — WITH DIFFERENT PATHS. WHICH ONE IS TRUE?
ONE CANONICAL TRAIL — THE SHELL'S. TEMPLATES LOST THE breadcrumbs PROP; AN AUDIT ITEM GUARDS IT FOREVER.
The tab rail stopped
70px before the edge.
CONTENT PADDING CUT THE RAIL SHORT — THE "TABS SPAN THE SURFACE" MODEL BROKE AT EVERY WIDE VIEWPORT.
OIPageContent PADDING-INLINE → 0. THE RAIL RUNS EDGE TO EDGE; BRICKS SELF-INSET INSTEAD.
The polish
files.
One page — the Deployment Overview — got nine consecutive fix waves (W80-FIX-A through I). This chapter is forensic UX: clipped glyphs, 10px jitter, phantom voids. The stuff users can't name but always feel.
4 PROBLEMS · INCLUDING THE TITLE THAT SURVIVED 20+ FIX ATTEMPTS →
The page title lost its head.
For 20+ prompts straight.
FONT-METRIC VARIANCE + overflow:hidden = ONLY THE BOTTOM HALF OF THE GLYPHS RENDERED. REPORTED 20+ TIMES, PATCHED AT THE WRONG LAYER EVERY TIME.
overflow:clip + overflow-clip-margin:4px — ELLIPSIS PRESERVED, ASCENDERS GET BREATHING ROOM. ROOT CAUSE, NOT SYMPTOM.
Title and tabs, 24px out of line.
On every detail page.
TAB-LIST PADDING PUSHED THE FIRST TAB 12→24px RIGHT OF THE TITLE'S LEFT EDGE. THE PAGE'S ALIGNMENT SPINE — BROKEN AT THE TOP.
.tab:first-child{padding-inline-start:0} — FIRST TAB TEXT SITS EXACTLY ON THE TITLE'S EDGE. SHIPPED GLOBALLY, NOT PER-PAGE.
Same row. Same chips.
Two different heights.
COPYABLE CHIPS WRAPPED THEIR GLYPH IN A BORDERED 24×24 ICON BUTTON → +10px. ONE ROW, TWO HEIGHTS, PERMANENT VISUAL NOISE.
BARE 16×16 HOVER-REVEAL COPY TRIGGER · TOKEN REBOUND control-sm → icon-sm · ALL 8 CHIPS: 24px FLAT.
A 1px cliff and a 48px hole,
under every header.
RULE-37 HAIRLINE COLLISION + A PADDING/MARGIN PILE-UP = A HARD DOUBLE LINE, THEN A DEAD BAND, ON EVERY PAGE.
ONE HAIRLINE + A THEME-ADAPTIVE GRADIENT FADE · VOID COLLAPSED VIA --oi-page-header-* TOKENS. THE SEAM BREATHES INSTEAD OF CUTTING.
States &
signals.
The quiet catastrophes: buttons that didn't look like buttons, text that vanished in one theme, colors that screamed ERROR when they meant "heads up." State design is trust design.
4 PROBLEMS · INCLUDING THE 83-FILE DARK MODE COLLAPSE →
Every Save button rendered
as plain text. Nobody filed a bug.
OIButton HAD NO variant="primary" — IT SILENTLY FELL BACK TO TEXT. 4 PRODUCTION CALLERS SHIPPED BROKEN. FOR MONTHS. UNFILED.
variant="primary" EXISTS · SOLID TEAL · AND RULE 69 NOW DEMANDS A STORY FOR EVERY VISUAL STATE, SO "SILENTLY FALLS BACK" IS EXTINCT.
Flip to dark mode.
Watch everything dissolve.
NO CARDS. NO BORDERS. NO BUTTONS. 83 FILES' WORTH OF CHROME — GONE. TOKENS WERE WRAPPED IN [data-theme="light"], WHICH NEVER MATCHES IN DARK.
FOUNDATION-CHAINED TOKENS MOVED BACK TO :root · RULE 68 REWRITTEN: SPLIT BY HOW A TOKEN RESOLVES, NOT BY SURFACE NAME.
There's a number on this card.
Find it.
--oi-fg-primary IS WHITE IN LIGHT MODE (IT'S FOR TEXT ON BRAND SURFACES). PUT IT ON A NEUTRAL CARD → INVISIBLE HERO NUMBER.
DECISION MATRIX LOCKED: BRAND BG → fg-primary · TINT → fg-brand · NEUTRAL → fg-inverse. THE TRAP IS DOCUMENTED & GREPPABLE.
Overcommitted capacity
screamed OUTAGE.
OVER-ALLOCATION PAINTED RED (= HARD ERROR) · "AVAILABLE" PAINTED GREY (= LOOKS BROKEN/MISSING). USERS SAW AN OUTAGE THAT WASN'T.
PALETTE LOCKED: PURPLE=HEALTHY · ORANGE=WARNING · BAR IS NEVER RED · TOKENS RENAMED TO MATCH THE TOOLTIP WORDS.
Information
design.
Where UX stops being pixels and starts being respect for attention: what to show once, what to hide behind a menu, what a chart owes its reader, and who gets left out when a link isn't really a link.
4 PROBLEMS · FROM THE ANTI-PATTERN CATALOG →
"Total members: 24."
Yes. We heard you. Three times.
SAME DATUM. HERO + KPI + DONUT. CLICK THE AUDIT TO SEE WHAT A REVIEWER SEES.
ONE AUTHORITATIVE SURFACE PER DATUM (A-001). THE RECLAIMED SPACE NOW SAYS SOMETHING NEW.
Six header actions.
Rollback one mis-click away.
6 ACTIONS SIDE-BY-SIDE, 3 OF THEM DESTRUCTIVE. OVERFLOWS ON NARROW SCREENS; THE PRIMARY DROWNS; "ROLLBACK" SITS NEXT TO "EDIT."
ONE PRIMARY CTA EXPOSED · ≥3 SECONDARIES COLLAPSE TO "MORE" · DESTRUCTIVE ALWAYS LIVES IN THE OVERFLOW, BEHIND A CONFIRM. TRY THE MENU →
Wrong ordinals. A missing bar.
Would you trust this data?
"21th" · "23th" · FINAL BAR SILENTLY DROPPED · LABELS ON THE WRONG AXIS · 30 CATEGORIES CRAMMED IN HORIZONTAL MODE. 7 BUGS TOTAL.
ALL 7 FIXED VIA CHROME FORENSICS · STORY FILE CUT 23 EXPORTS → 7 CANONICAL ONES SO THE BUGS HAVE NOWHERE TO HIDE.
Press TAB.
Watch who gets skipped.
IT'S A <span onClick> WITH cursor:pointer. MOUSE-ONLY. KEYBOARD & SCREEN-READER USERS: IT DOESN'T EXIST.
NOW IT'S <OIButton variant="link"> — REAL FOCUS, REAL role, REAL ENTER/SPACE. EVERYONE GETS THE SAME PAGE.
Fixing 20 problems is easy.
Making them unrepeatable is the work.
Every fix became a law
Rule 35 (templates own no layout), Rule 37 (no adjacent hairlines), Rule 69 (every visual state gets a story), Rules 82–84 (brick spacing). Cited by number in every review.
Every bug got a registry entry
The title clip, the fg-primary trap, the dark-mode collapse — each catalogued with the exact grep that catches its return. Regressions die in CI, not in front of users.
The builder never grades itself
A fresh-context AI audits every wave against the rules and the registry. "Looks fine to me" stopped being an argument.
BEFORE: 20+ PROMPTS TO FIX ONE TITLE · AFTER: ONE PROMPT, ROOT CAUSE, RULE, REGRESSION GUARD