Latte UI — design system v2.0
Who builds a design system for his portfolio?
Me. Obviously me.
One user. Zero stakeholders. A full token architecture regardless. It’s either discipline or a diagnosable condition — either way, it’s why the site looks like this, and why I’d be useful on your team.
- 8
- type steps, each a whole treatment
- 2
- themes, neither an afterthought
- 4
- durations, nothing between
- 0
- colours in the chrome
A monochrome, single-face system. Black and white carries the structure; colour belongs to the work, never the chrome. Toggle the theme and check both — light is a full peer, not an afterthought.
01 — Color
Cool neutral, two themes
Near-black carries a faint blue cast so surfaces separate without gaining warmth.
bg
--bg
surface
--surface
card
--card-bg
card-hover
--card-hover
border
--border
border-strong
--border-strong
text
--text
text-secondary
--text-secondary
decor
--decor
inverse
--inverse-bg
Text ramp — two steps
Only two greys are legal for reading. Labels separate from prose by being mono, uppercase and 11px — not by getting dimmer. decor is barred from text at any size.
text — AA · AAA. Running prose and headings.
text-secondary — AA at 11px and up. Lead paragraphs, captions, meta.
decor — rules, dots, disabled states. Never text, including this line: it fails contrast at any size.
Status — neutral by default
Error red is the single non-neutral permitted in the chrome, and only at label scale. Success is communicated by state change, not hue.
Scoped accents
Each study keeps its accent in the schema, injected as scoped variables inside /work/[slug] and nowhere else. Nav, footer, buttons and index cards stay monochrome.
SirFolajomi OS
#E3A63A
Koordinar
#0B63E5
SoftSuite HR
#16A34A
PlantBuddy
#2E9E4F
02 — Typography
One face, eight steps
Cabinet Grotesk does display and body alike — weight and tracking do the work a second family would. JetBrains Mono is reserved for labels, metrics and code.
Display
800 · clamp(3rem, 8.5vw, 7.5rem) · -0.045em · 0.92
Part-time designer
H1
700 · clamp(2.5rem, 5.5vw, 4.25rem) · -0.035em
Selected work, 2021–2025
H2
700 · clamp(1.875rem, 4vw, 3rem) · -0.03em
Three strategic decisions
H3
700 · clamp(1.375rem, 2vw, 1.75rem) · -0.02em
Free tier as the product
Body lg
400 · 19px · 1.6 · text-secondary
What I’ve learned through fatherhood and design is the same lesson: clarity over cleverness, every time. A toddler doesn’t care about your elegant interaction pattern. A user in a rush doesn’t care about your spring animation. Both just need to know: what happens when I tap this?
Body
400 · 17px · 1.65 · max 68ch
When I sit down to design, I’m not thinking about screens. I’m thinking about the person on the other side — what they came to do, what’s stressing them out, what would make them feel like someone actually gets it. Then I make that feeling into an interface.
Small
15px
Captions, card meta, footnotes.
Label
mono · 11px · uppercase · 0.1em
Case study — 02 / Koordinar
Metric
800 · tabular figures
5min→30s
Weights in rotation
- Regular 400
- All body copy and prose.
- Medium 500
- Buttons, nav, tags, UI.
- Bold 700
- H1–H3, card titles.
- Extrabold 800
- Display, metrics, wordmark.
Light 300 is unused — too fragile at portfolio scale. Black 900 is reserved. Neither is loaded.
03 — Space & shape
4px rhythm, hairlines over shadows
Airy by intent. Every gap is a step on the scale — no in-between values. Depth in dark mode comes from surface value plus a 1px border; shadow is used only where something genuinely floats.
4
8
12
16
24
32
48
64
96
128
- Page gutter
- 24 / 40px
- Max content
- 1240px
- Prose column
- 680px · 68ch
- Grid gap
- 24px
- Section pad · desktop
- 112–160px
- Section pad · mobile
- 72–96px
- Card padding
- 24 / 32px
- Heading → body
- 16px
Radii
xs — 3px
Swatches, inline chips
sm — 4px
Buttons, tags, inputs
md — 6px
Cards, panels, media
lg — 10px
Modals, device frames
full
Dots, avatars, island only
Elevation
Flat — level 0
1px border, no shadow.
Raised — level 1
Value shift. Hover state of a card.
Floating — level 2
Nav island, command palette.
04 — Primitives
Restyled, same API
Every component keeps its props. Only the tokens beneath them changed.
Button
Fill inverts to the text colour. No brand fill — contrast is the emphasis. Radius 4, 1px lift on hover. Spec sizes are md 40px and sm 32px; lg (48px) is an addition for hero CTAs, which read as tags at 40px with 15px text.
Tag
Radius 4, was a pill. The status dot is now white rather than green — the only visual break from the old system.
Card
01
4,000 → 14
records reduced to the ones a human actually owes a reply
SirFolajomi OS
An AI-generated console that ran fine and read like noise. Rebuilt on a token system written before any screen — four thousand records now resolve to the fourteen that need a person today.
02
12 chats → 1
places the group has to look
Koordinar
Group trips die in the planning phase. Koordinar keeps voting, dates and destination intel inside the trip itself, so decisions happen where the context already lives.
Highlight
Decision 01
Free tier as the product
A left hairline replaces the old accent bar — the same emphasis without importing colour into the chrome.
Marquee
05 — Motion
Two curves, four durations
Every transition answers “what changed?”. If it can’t, it doesn’t ship. Anything outside these curves is not permitted.
ease-out-expo
.16, 1, .3, 1
Entrances, hovers, reveals.
ease-in-out-quart
.76, 0, .24, 1
Symmetric moves — theme swap, page transitions.
- 120
- hover
- 200
- state
- 400
- reveal
- 700
- page
What’s running now
Every piece is conditional on the environment, so this reports live state rather than claiming it. Turn on Reduce Motion in system settings and these should go dark.
- Smooth scrollinactive
- off under reduced motion
- Char revealinactive
- hero only, once per session
- Section revealinactive
- resting state under reduced motion
- Loaderinactive
- once per session
- Cursorinactive
- fine pointer with hover only
- Scroll cueactive
- fades after first scroll
Char reveal
Per character, 18ms stagger, 400ms expo. Hero headline only, once per session — the replay button exists because you can’t otherwise see it twice.
Latte.
Section reveal
14px rise + fade, 400ms, 80ms stagger, fires once at 20% viewport entry.
Item 1
+0ms
Item 2
+80ms
Item 3
+160ms
Item 4
+240ms
Scroll cue
A segment travelling along a hairline, which fades out permanently on the first scroll event. It’s pinned here — you had to scroll to reach this section, so a real one would have hidden itself before you arrived.
Cursor
Dot follows at 1:1, ring lags at 0.12 lerp and expands to a label over anything carrying data-cursor. Desktop pointer only. Hover the panel below.
Hover me
Loader
620ms hold on the wordmark, then a 700ms curtain lift on quart. It plays once per session, so this replays it properly — full viewport, the real component, the session flag put back afterwards.
Removed under reduced motion — which is why the button is disabled.
06 — Interactive demos
The product, not a picture of it
Every case study opens with a working slice of the thing it describes. One shell carries all four: it loads nothing until the demo is within 400px of the viewport, hints after six idle seconds that the panel is live, and can always be put back to where it started.
Koordinar
Move the trip and watch the forecast and holiday flags follow. Then open Group votingand cast the seventh vote — it resolves in place and lands on the itinerary beside it, which is the case study’s written claim demonstrated rather than asserted.
Shift the dates, then cast a vote.
Indian Trip
In progressDelhi, India · December 02 – December 11, 2025
7 travellers
Destination information updates automatically when the dates move
Public holidays in India
No public holidays fall inside these dates.
Weather forecast
Tue, Dec 02
24°
Haze
Wed, Dec 03
23°
Haze
Thu, Dec 04
22°
Sunny
Fri, Dec 05
21°
Sunny
Sat, Dec 06
19°
Haze
Sun, Dec 07
20°
Sunny
SoftSuite HR
One control carries both claims. Dragging the licence count moves the money andflips the traffic light — the two things an admin used to work out separately, one with a calculator and one by opening three screens. It opens at 94% capacity, so there’s a decision waiting rather than a resting state.
Add a module, change the license count.
Upgrade Subscription
PaperSoft Limited · 470 staff on the system
Licences
500
470 of 500 seats used · 94%
Payment frequency
Cost per user / month
₦4,500
- System Administrator₦4,500
- × 500 licences ₦2,250,000
Due today
₦2,250,000
Prorated from today · renews 12 Jan 2026
SirFolajomi OS
Open a bucket and the ledger underneath reports what each channel put into it, ending in the sum — a verdict you can walk back to raw figures is trustworthy, one you can’t is only confident. Then tick Count policy silence as dropped to see the number the generated console showed, and why nobody read it.
Open a bucket, then walk back to the raw figure.
Inbound
Did we drop anyone? Four channels, one rollup
The verdict
every at-risk bucket, all four channels
Silent-by-design is never counted as dropped: the sandbox lock and vendor mail are policy, not misses.
Channel ledger · 4
today’s response meter · 7-day pair
Email
poller · SQL, always live
12 responded of 15 received today
12 responded · 3 unanswered
103 received 7d
39 vendor mail 7d
3
unanswered > threshold
oldest waiting 297h 36m
WhatsApp
poller · SQL, always live
0 responded of 1 received today
0 responded · 1 unanswered
1 received 7d
0 responded 7d
1
unanswered > threshold
oldest waiting 443h 52m
IG comments
Hermes IG-comment exporter · snapshot bridge
53 responded of 61 received today
53 responded · 8 unanswered
3,955 keyword leads, all time
52 sampled for a human
8
stuck — actionable, window open
IG DMs
Hermes DM exporter · snapshot bridge
0 responded of 2 received today
0 responded · 2 unanswered
0 responded 7d
100 sandbox-gated today
2
other silent — needs a look
0 escalated, human owed
need a human now3 + 1 + 8 + 2 =14
Amber is the signature: if nothing is amber, the morning is clear.
PlantBuddy
The one phone product of the four, so it’s also the phone frame in use. Identify the plant and the schedule writes itself — the card counting what wasn’tasked is the study’s metric made visible, since an absence can’t be demonstrated by leaving it out. Then clear the day’s tasks: the empty state is the argument, and it only lands because you can reach it.
Identify a plant, then check today’s tasks.
Add a plant
Point the camera at it. That's the whole setup.
Frames
Two variants. The frame stays monochrome in both — it’s site chrome, and §01 keeps colour on the product inside it. The phone shell’s 30px corners are the one addition to §04’s radius table: a device is an object, not a panel.
Browser · 10px
Phone · 30px
Accent, derived
A product colour is rarely legible as text on both themes — Koordinar’s blue sits at 2.4:1 on a dark card. Everything read uses --accent-ink, mixed toward --text, so the tint survives and the contrast does too. Raw --accent-base is for fills, rules and icons only.
SirFolajomi OS
Legible on both themes
Koordinar
Legible on both themes
SoftSuite HR
Legible on both themes
PlantBuddy
Legible on both themes