Window Lab OS
Design system
One token file, two surfaces. Contrast ratios below are measured against the page ground #FAF9F7, not estimated. Curves and durations come from the animation tables rather than from taste.
Principles
- 1The creative is the content; the interface is the mount. No chrome hue within one step of a client's work. Grounds adjacent to media are achromatic, platform marks are monochrome.
- 2Status carries on three channels: weight, hue, word. Fill weight encodes pipeline position, so commitment reads before the label does.
- 3Every state name is the past tense of the action that produced it. Approve → Approved. The copy rule is structural, not a style guide to remember.
- 4Undo beats confirm. Publishing spends money. Both surfaces execute immediately and open a five-second window.
- 5The interface always names who holds the ball. AI proposes, agency reviews, client approves, system executes. Machine output is marked and cannot leave Draft without a person.
Colour
Six hues total. That is the whole budget — the content brings its own colours.
Base palette
ink
paper
cobalt
verdant
ember
signal
Warm neutral ramp
All chrome, text, borders and panels. Warm, so an eight-hour session doesn't feel clinical. Numbers are contrast against paper.
0
1.05
50
1.05
100
1.15
200
1.34
300
1.76
400
3.40
500
4.82
600
6.81
700
10.09
800
14.15
900
17.77
- Body text: 700 or darker.
- Muted text: 500 — 4.82:1, the lightest value that clears AA.
- Control borders, the edge that identifies an input: 400 — 3.40:1, clears SC 1.4.11.
- Gridlines and decorative rules: 200. Exempt from 3:1 because they don't identify a component. If a boundary is the only thing telling you a control exists, it must be 400.
Achromatic stage
Zero chroma. Used only where client creative is displayed — the studio canvas and every media viewer, dark regardless of app theme. A warm-tinted ground shifts how a warm static reads.
1C1C1C
232323
2E2E2E
3D3D3D
6E6E6E
B4B4B4
EDEBE8
Status system
Fill weight is the primary channel. It ladders with commitment: dashed → outline → soft → solid.
| Chip | Compact | Weight | Owner | Means |
|---|---|---|---|---|
| Draft | dashed | Agency | Not submitted. Editable. | |
| In review | outline | Agency | Agency QA holds it. | |
| Changes requested | soft | Author | Someone must act. | |
| Approved | soft | Client | Permission granted. Nothing is live. | |
| Scheduled | soft | System | Committed to a time. | |
| Published | solid | System | Live. Spending. Irreversible. | |
| Failed | solid | System | Broken. Needs a person. |
Approved is green; Published is not.
In review and Scheduled share the cobalt hue.
Small size, and with owner
AI provenance is orthogonal
A thing can be machine-made in any state, so provenance is a glyph and never a colour — it must not compete with status.
Platform marks
Monochrome by rule. A month view holds 40+ posts and brand colour turns it into confetti that fights the status system. Colour returns only where the platform is the subject.
Default — monochrome
Where the platform is the subject
Focus
Two mechanisms, because focus lands on client artwork and a single hue cannot survive an arbitrary photo.
Over known surfaces
2px #7B71E8, 2px offset. 3.72:1 on paper, 4.36:1 on stage-900.
Over media
Double ring: 2px ink inner, 4px paper outer. A cobalt ring over a mid-gray photo measures 1.01:1 — invisible. Ink and paper measure 4.73:1 and 3.75:1 against that same ground, so one always wins.
SC 2.4.11 Focus Not Obscured
scroll-padding-top to the header height, so keyboard focus never lands underneath the toolbar or a sticky table header.Typography
Two ramps, not one. A ratio tight enough for dense UI leaves big numbers timid; a ratio dramatic enough for numbers leaves gaps in the text range.
Text ramp — Inter, tight ~1.1 steps
text-microCOLUMN HEADERtext-metatext-denseTrail Runner 2 — colourway droptext-bodySend for approvaltext-lgNew season, same 4mm drop.text-titleApproval queuetext-pageContent calendarNumeric ramp — wide ~1.3 steps, tabular
Tabular figures are on globally, not per component: in a media-buying tool numbers are compared column to column all day. Inter's optical size axis handles the large end, which is why there is no separate display family.
Display — Source Serif 4
Report covers, client-facing headlines, empty states, sign-in. Never below 20px, never in chrome. This face is a token — --font-display is the documented swap point for white-label, and nothing else depends on its metrics.
Meta carried the month
August performance
Autumn Drop 04 — launch week
Mono — JetBrains Mono
Mobile floor is 13px, and 13 is metadata only.
Spacing and density
Base unit 4. Scale: 2 4 6 8 12 16 20 24 32 40 48 64 80.
| Client | Post | Channel | Status | Goes live |
|---|---|---|---|---|
| LAlumen | Trail Runner 2 — colourway drop | ScheduledScheduled | 14 Sep, 09:00 | |
| VCverano | The Ethiopia lot is back | ApprovedApproved | 7 Sep, 14:00 | |
| MFmeridian | Coach Yara on progressive overload | In reviewIn review | 7 Sep, 18:00 | |
| SSsaffron | New autumn menu — first pour | Changes requestedChanges requested | 8 Sep, 12:30 | |
| KOkestrel | Pack light, stay out longer | PublishedPublished | 5 Sep, 10:15 |
Token values
| Token | Comfortable | Compact |
|---|---|---|
| --row-h | 44 | 32 |
| --cell-px | 12 | 8 |
| --cell-py | 12 | 6 |
| --panel-p | 20 | 12 |
| --section-gap | 24 | 16 |
| --hit | 32 | 24 |
Compact stops at a 24px hit area.
Motion
The do-not-animate list is longer than the animate list, and that is deliberate.
Does not animate
Anything a power user triggers dozens of times a day.
- —Calendar grid render and scroll
- —Filter application, table sort
- —Keyboard navigation between cells
- —The density toggle
- —The command palette
- —Tab switches in the studio
- —Count-up on analytics numbers
- —Celebration on approve or publish
Curves and durations
| What | Purpose | Properties | Curve | ms |
|---|---|---|---|---|
| Button press | feedback | transform: scale(0.97) | ease-out | 100 |
| Status chip changing state | state indication | background-color, color, border-color | ease | 140 |
| Dropdown, filter menu | feedback | scale(0.96)→1, opacity, origin at trigger | ease-out | 180 |
| Skeleton → content | prevent jarring change | opacity | ease-out | 180 |
| Toast in and out | feedback | translateY(100%), opacity — transition, not keyframes | ease-out | 200 |
| Comment pin landing | feedback | scale(0.9)→1, opacity | ease-out | 160 |
| Card moving between queue states | spatial consistency | full transform string | ease-in-out | 240 |
| Approve → next item (mobile) | spatial consistency | transform, opacity, 60ms stagger | ease-out | 240 |
| Bottom sheet (mobile) | spatial consistency | translateY(100%) | ease-drawer | 320 |
| Undo countdown bar | information | scaleX(1)→0 | linear | 5000 |
The undo window is the safety mechanism, not a dialog.
prefers-reduced-motion the bar is replaced by a numeric countdown, because the information has to survive even when the motion doesn't.Components
Every repeated piece, with its states.
Button
| Intent | Default | Hover | Focus | Active | Loading | Disabled |
|---|---|---|---|---|---|---|
| primary | ||||||
| approve | ||||||
| secondary | ||||||
| ghost | ||||||
| danger |
Input
Metric tile
Annotation layer
Pins are real buttons in tab order — a comment you cannot reach by keyboard is a comment the reviewer will miss. Focus over media uses the double ring. The video variant anchors the same pin to a timestamp instead of a coordinate.
Empty, error, loading
Not afterthoughts. This product shows “no posts scheduled” and “connection expired” constantly.
Nothing waiting for review
Everything the team drafted has moved on. New AI drafts land here automatically.
No posts scheduled in September
Nothing is queued for any client this month. Draft a post, or let AI propose a week.
Instagram connection expired
Posts to Meta stopped publishing at 06:12 today. Reconnect to resume — scheduled posts will go out in order.
Google Ads is 14 hours behind.
Their API has been rate-limiting since 21:30 yesterday.
Loading
Skeletons match the shape of what is arriving, so the layout doesn't jump when it lands. The swap is opacity only, 180ms.
Writing
UI copy is part of the design.
| Rule | Write | Not |
|---|---|---|
| An action keeps its name through the flow | Publish → “Published to Meta and TikTok.” | Publish → “Success!” |
| Errors say what happened and how to fix it | “Instagram connection expired. Reconnect to keep publishing.” | “Something went wrong.” |
| Empty states invite an action | “No posts scheduled this week. Draft one.” | “No data.” |
| Plain verbs, sentence case | “Send for review” | “Submit for Review Process” |
| Name the owner, not the system | “Goes to Nadia Rahman, then to the client.” | “Enters approval workflow.” |
The state names are the copy system.