This document turns the visual direction in DESIGN-LANGUAGE.md into an implementation system that can unify:
apps/app(OpenWork app)ee/apps/den-web(OpenWork Cloud / Den web surfaces)ee/apps/landing(marketing + product storytelling)
The goal is not to create three similar styles. The goal is one OpenWork design system with a few environment-specific expressions.
Today the product already has the beginnings of a system, but it is split across:
- app-specific CSS variables in
apps/app/src/app/index.css - Tailwind theme setup in
apps/app/tailwind.config.ts - Radix color tokens in
apps/app/src/styles/colors.css - repeated utility-class decisions across app, Cloud, and landing
That creates three problems:
- the app and Cloud can feel related but not identical
- visual decisions are made at the screen level instead of the system level
- tokens, primitives, and page composition rules are not clearly separated
This file defines the missing structure.
OpenWork should use a three-layer design system:
Raw design tokens:
- color
- typography
- spacing
- radius
- shadow
- motion
These are the only values components should depend on directly.
Product-meaning tokens:
surface.pagesurface.panelsurface.sidebartext.primarytext.secondaryborder.subtleaction.primary.bgstate.hoverstate.selected
These should map foundation tokens into product meaning.
Reusable building blocks:
- Button
- Card
- Input
- Modal shell
- Sidebar shell
- List row
- Status pill
- Section header
- Empty state
Pages should mostly compose these primitives, not invent their own visual logic.
DESIGN-LANGUAGE.md= visual philosophy and qualitative rulesDESIGN-SYSTEM.md= implementation structure and migration plan
If there is a conflict:
DESIGN-LANGUAGE.mddecides what the product should feel likeDESIGN-SYSTEM.mddecides how to encode that in tokens and primitives
OpenWork has three main UI contexts:
- App expression — denser, flatter, operational
- Cloud expression — still operational, slightly more editorial and roomy
- Landing expression — more atmospheric, but still clearly the same product family
These should differ mostly in:
- spacing density
- shell scale
- amount of atmosphere
- page composition
They should not differ in:
- brand color logic
- button language
- border philosophy
- type hierarchy
- selection behavior
We should converge on a small token set that works everywhere.
Use Radix as the raw palette source, but not as the public API for product styling.
Raw palette source:
- Radix gray/slate/sage for neutrals
- Radix red/amber/green/blue for semantic states
Canonical semantic token set:
--ow-color-page--ow-color-surface--ow-color-surface-subtle--ow-color-surface-sidebar--ow-color-border--ow-color-border-strong--ow-color-text--ow-color-text-muted--ow-color-text-subtle--ow-color-accent--ow-color-accent-hover--ow-color-hover--ow-color-active--ow-color-success--ow-color-warning--ow-color-danger
These should become the shared API across app and Cloud.
Existing app tokens already point in the right direction:
--dls-app-bg->--ow-color-page--dls-surface->--ow-color-surface--dls-sidebar->--ow-color-surface-sidebar--dls-border->--ow-color-border--dls-text-primary->--ow-color-text--dls-text-secondary->--ow-color-text-muted--dls-accent->--ow-color-accent--dls-accent-hover->--ow-color-accent-hover
We should migrate by aliasing first, not by breaking everything at once.
Typography should be systemized into roles, not ad hoc text sizes.
- display — rare marketing or hero usage
- headline — page and section headers
- title — card and object titles
- body — default reading text
- meta — labels, helper copy, secondary information
- micro — pills, badges, tiny metadata
- one main sans family across product surfaces
- medium weight does the majority of hierarchy work
- muted text is the default support color
- avoid large type jumps inside the app
OpenWork should use a consistent spacing scale instead of one-off values.
Recommended base scale:
- 4
- 8
- 12
- 16
- 20
- 24
- 32
- 40
- 48
- 64
- micro control padding: 8–12
- row padding: 12–16
- card padding: 20–24
- major section padding: 32–48
- page rhythm: 48–64 on roomy surfaces, 24–32 in dense app surfaces
Canonical radius roles:
--ow-radius-control— small controls and rows--ow-radius-card— cards and panels--ow-radius-shell— sidebars, large grouped containers, modal shells--ow-radius-pill— buttons, tabs, chips
Suggested mapping:
- control: 12px
- card: 16px
- shell: 24px–32px
- pill: 9999px
Shadow should be a named system with very few levels.
--ow-shadow-none--ow-shadow-control--ow-shadow-card--ow-shadow-shell
Default behavior:
- app: mostly
noneorcontrol - Cloud: mostly
none,control, occasionalcard - landing: selective
cardorshell
We should explicitly define a small primitive set shared across product surfaces.
- Primary button
- Secondary button
- Ghost button
- Destructive button
- Segmented pill / tab item
- Page shell
- Sidebar shell
- Card
- Quiet card
- Modal shell
- Section divider
- Text input
- Textarea
- Select
- Checkbox/radio treatment
- Inline field group
- Sidebar row
- List row
- Topbar item
- Breadcrumb / section tab
- Status pill
- Banner
- Empty state
- Toast
Prefer:
bg-[var(--ow-color-surface)]text-[var(--ow-color-text-muted)]
Over:
bg-whitetext-gray-500
Raw grays are still acceptable for temporary legacy usage, but new primitives should use semantic tokens.
Page files can compose primitives and choose layouts. They should not invent new button styles, new shadow rules, or new selection patterns.
Radix is the palette source. OpenWork tokens are the product API.
Even when implementations differ, the primitive names and behaviors should match.
Example:
Buttonin appButtonin den-web
Both should resolve to the same token logic and visual rules.
Do not redesign everything at once. Use this sequence.
- create canonical semantic tokens
- alias current app tokens to the new token names
- document primitive families and approved variants
Start with:
- Button
- Card
- Input
- Sidebar row
- Modal shell
These give the largest visual consistency gain.
Standardize:
- page background
- sidebar shell
- panel/card shell
- list row selection
- headers and section spacing
Prioritize:
- workspace/session surfaces in
apps/app - Cloud dashboard shells in
ee/apps/den-web - share/package/connect flows in
apps/app
As primitives stabilize:
- reduce repeated one-off class recipes
- replace raw gray classes in repeated patterns
- collapse duplicate card/button/input styles into primitives
If we implement this system, the likely canonical files should be:
DESIGN-LANGUAGE.md— philosophyDESIGN-SYSTEM.md— system rules and migration planapps/app/src/app/index.css— initial token host for app runtimeapps/app/tailwind.config.ts— Tailwind token exposureapps/app/src/app/components/button.tsx— canonical action primitive startapps/app/src/app/components/card.tsx— canonical surface primitive startapps/app/src/app/components/text-input.tsx— canonical field primitive start
Later, a shared package may make sense, but not before the token model is stable.
The smallest safe implementation path is:
Introduce canonical --ow-* aliases in apps/app/src/app/index.css without removing --dls-* yet.
Refactor Button, Card, and TextInput to consume shared semantic tokens.
Use the Den dashboard shell as the reference for:
- sidebar shell
- row selection
- neutral panel rhythm
Restyle one OpenWork app screen fully using the system to prove the direction.
Recommended pilot screens:
apps/app/src/app/pages/settings.tsx- session/workspace sidebar surfaces
- share workspace modal
We will know this is working when:
- app, Cloud, and landing feel obviously from the same product family
- a new screen can be built mostly from existing primitives
- visual changes happen by adjusting tokens or primitives, not by editing many pages
- selection, buttons, cards, and inputs behave consistently everywhere
- raw color classes become uncommon outside truly local exceptions
This system should not:
- introduce a trendy visual reboot disconnected from the current product
- replace the OpenWork mood described in
DESIGN-LANGUAGE.md - depend on a large new dependency just to manage styling
- force a shared package too early
- block incremental improvements until a perfect system exists
The correct approach is a strong design system built through small, boring, compounding steps.
If continuing from this doc, the best next change is:
- add
--ow-*semantic token aliases inapps/app/src/app/index.css - standardize
Button,Card, andTextInput - then restyle one app shell to match the calmer Den dashboard direction
That gives a real system foothold without a broad rewrite.