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
Cabinet GroteskJetBrains MonoCool neutral · dark default4–6px radii

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

Open to product design rolesInactive — decor dot, staticError — forms only

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.

As a link

Tag

Radius 4, was a pill. The status dot is now white rather than green — the only visual break from the old system.

Open to product design rolesDesign SystemProduct DesignFront-end Build2024

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.

Design SystemProduct DesignFront-end Build

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.

Product DesignLogo Design

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

Product DesignDesign EngineeringSystemsPrototyping

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 progress

Delhi, 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

An interactive slice of a Koordinar trip. Changing the travel dates refreshes the weather forecast and flags public holidays that fall inside the new window. Casting a vote on an activity resolves it in place, without leaving the trip.

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

Nearly full30 seats spare at 500 licences
Select modules

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

An interactive slice of the SoftSUITE upgrade flow. Selecting modules, changing the license count and switching payment frequency all recalculate the total due immediately, replacing the manual arithmetic the old interface required.

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.

An interactive slice of the rebuilt Inbound screen. The verdict resolves four thousand records into three buckets — owed, unreachable, silent by design — and the channel ledger underneath lets you walk each number back to its source, which is what makes the verdict trustworthy rather than merely confident.

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.

An interactive slice of PlantBuddy. Identifying a plant generates its care schedule automatically, and the daily task list then shows only what is actionable right now, with single-tap completion.

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

#E3A63A

Koordinar

Legible on both themes

#0B63E5

SoftSuite HR

Legible on both themes

#16A34A

PlantBuddy

Legible on both themes

#2E9E4F
Esc