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

  1. 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.
  2. 2Status carries on three channels: weight, hue, word. Fill weight encodes pipeline position, so commitment reads before the label does.
  3. 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.
  4. 4Undo beats confirm. Publishing spends money. Both surfaces execute immediately and open a five-second window.
  5. 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

#14120F

Text, dark chrome, Published

paper

#FAF9F7

App ground

cobalt

#4338CA

Accent + system-holds-it

verdant

#12703F

Approved

ember

#96500A

Changes requested

signal

#B0231C

Failed, destructive

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.

ChipCompactWeightOwnerMeans
DraftDraftdashedAgencyNot submitted. Editable.
In reviewIn reviewoutlineAgencyAgency QA holds it.
Changes requestedChanges requestedsoftAuthorSomeone must act.
ApprovedApprovedsoftClientPermission granted. Nothing is live.
ScheduledScheduledsoftSystemCommitted to a time.
PublishedPublishedsolidSystemLive. Spending. Irreversible.
FailedFailedsolidSystemBroken. Needs a person.

Approved is green; Published is not.

The naive palette makes both green and destroys the distinction the product turns on. Approved means permission exists and nothing has happened. Published means money is moving and it cannot be taken back. Published is the only near-black chip in the system, so a published row is identifiable in peripheral vision.

In review and Scheduled share the cobalt hue.

They separate on weight — outline versus soft fill. Both mean “in the machine, nothing for you to do”. The hue doing double duty as the accent is why the interface can hold a six-hue budget at all.

Small size, and with owner

DraftIn reviewChanges requestedApprovedScheduledPublishedFailed
In review· AgencyApproved· ClientFailed· System

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.

AI draftDraftAI draft

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

MetaGoogle AdsTikTokSnapchatLinkedInYouTubeX

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.

Approved

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

Every scroll container under sticky chrome sets 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-micro11 / 14 · +0.06emCOLUMN HEADERUppercase only. Web only.
text-meta12 / 16Scheduled for 14 Sep, 09:00Timestamps, secondary. Web only.
text-dense13 / 18Trail Runner 2 — colourway dropThe web workhorse.
text-body14 / 20Send for approvalDefault UI, buttons, inputs.
text-lg16 / 24New season, same 4mm drop.Mobile body, dialog body.
text-title20 / 28Approval queueSection headings.
text-page24 / 32Content calendarPage titles.

Numeric 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.

96,410

num-sm · 24px

96,410

num-md · 32px

96,410

num-lg · 44px

96,410

num-xl · 56px

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

utm_campaign=autumn-drop-04&utm_source=meta&utm_medium=paid_social

Mobile floor is 13px, and 13 is metadata only.

Body is 16. The 11 and 12 steps do not exist on the mobile surface — a client reading on a phone in daylight is not a power user at a 27-inch display.

Spacing and density

Base unit 4. Scale: 2 4 6 8 12 16 20 24 32 40 48 64 80.

Try it:
Type size and contrast do not change. Only space does.
ClientPostChannelStatusGoes live
LAlumenTrail Runner 2 — colourway dropScheduledScheduled14 Sep, 09:00
VCveranoThe Ethiopia lot is backApprovedApproved7 Sep, 14:00
MFmeridianCoach Yara on progressive overloadIn reviewIn review7 Sep, 18:00
SSsaffronNew autumn menu — first pourChanges requestedChanges requested8 Sep, 12:30
KOkestrelPack light, stay out longerPublishedPublished5 Sep, 10:15

Token values

TokenComfortableCompact
--row-h4432
--cell-px128
--cell-py126
--panel-p2012
--section-gap2416
--hit3224

Compact stops at a 24px hit area.

That is the floor set by WCAG 2.2 SC 2.5.8 Target Size (Minimum). Nothing goes below it, which is the reason the ladder stops where it does rather than at some arbitrary aesthetic limit. Density is web-only; mobile has one density and a 44px minimum throughout.

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

--ease-out
cubic-bezier(0.23, 1, 0.32, 1)
--ease-in-out
cubic-bezier(0.77, 0, 0.175, 1)
--ease-drawer
cubic-bezier(0.32, 0.72, 0, 1)
--dur-press
100ms
--dur-fast
140ms
--dur-base
180ms
--dur-slow
240ms
--dur-sheet
320ms
--dur-undo
5000ms
WhatPurposePropertiesCurvems
Button pressfeedbacktransform: scale(0.97)ease-out100
Status chip changing statestate indicationbackground-color, color, border-colorease140
Dropdown, filter menufeedbackscale(0.96)→1, opacity, origin at triggerease-out180
Skeleton → contentprevent jarring changeopacityease-out180
Toast in and outfeedbacktranslateY(100%), opacity — transition, not keyframesease-out200
Comment pin landingfeedbackscale(0.9)→1, opacityease-out160
Card moving between queue statesspatial consistencyfull transform stringease-in-out240
Approve → next item (mobile)spatial consistencytransform, opacity, 60ms staggerease-out240
Bottom sheet (mobile)spatial consistencytranslateY(100%)ease-drawer320
Undo countdown barinformationscaleX(1)→0linear5000

The undo window is the safety mechanism, not a dialog.

Publish and Approve execute immediately and both open five seconds to take it back. The depleting bar is the only continuously-moving element in the product and the only motion carrying information rather than confirming it — linear, because a countdown that eases lies about how much time is left. Under 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

IntentDefaultHoverFocusActiveLoadingDisabled
primary
approve
secondary
ghost
danger
Sizes

Input

Enter a date in the future.

Metric tile

Spend (AED)
96,410
9.4%vs August
Blended ROAS
3.5×
6.2%vs August
Conversions
2,977
3.4%vs August
Loading

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.

0:06 / 0:24

Resolved pins go neutral and keep a check glyph, so “done” is never carried by colour alone.

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.

Loading

Skeletons match the shape of what is arriving, so the layout doesn't jump when it lands. The swap is opacity only, 180ms.

Publishing to 3 channels

Writing

UI copy is part of the design.

RuleWriteNot
An action keeps its name through the flowPublish → “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.

Approve → Approved. Schedule → Scheduled. Request changes → Changes requested. Send for review → In review. Because each state is the past tense of its action, a button and the toast it produces always share a stem — nobody has to remember the rule for it to hold.