11 KiB
OpenWork Design System
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.
1. Why this exists
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.
2. System model
OpenWork should use a three-layer design system:
Layer 1: Foundations
Raw design tokens:
- color
- typography
- spacing
- radius
- shadow
- motion
These are the only values components should depend on directly.
Layer 2: Semantic tokens
Product-meaning tokens:
surface.pagesurface.panelsurface.sidebartext.primarytext.secondaryborder.subtleaction.primary.bgstate.hoverstate.selected
These should map foundation tokens into product meaning.
Layer 3: Component primitives
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.
3. Relationship to existing docs
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
4. Core principle: one system, three expressions
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
5. Canonical token architecture
We should converge on a small token set that works everywhere.
5.1 Foundation color tokens
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
5.2 Semantic color tokens
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.
5.3 Current mapping from app tokens
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.
6. Typography system
Typography should be systemized into roles, not ad hoc text sizes.
Roles
- 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
Shared rules
- 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
7. Spacing system
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
Usage guidance
- 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
8. Radius system
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
9. Shadow system
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
10. Component primitive families
We should explicitly define a small primitive set shared across product surfaces.
10.1 Action primitives
- Primary button
- Secondary button
- Ghost button
- Destructive button
- Segmented pill / tab item
10.2 Structure primitives
- Page shell
- Sidebar shell
- Card
- Quiet card
- Modal shell
- Section divider
10.3 Input primitives
- Text input
- Textarea
- Select
- Checkbox/radio treatment
- Inline field group
10.4 Navigation primitives
- Sidebar row
- List row
- Topbar item
- Breadcrumb / section tab
10.5 Feedback primitives
- Status pill
- Banner
- Empty state
- Toast
11. System-first implementation rules
Rule 1: prefer semantic tokens over raw utility colors
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.
Rule 2: page code should not define new visual language
Page files can compose primitives and choose layouts. They should not invent new button styles, new shadow rules, or new selection patterns.
Rule 3: Radix stays underneath the system
Radix is the palette source. OpenWork tokens are the product API.
Rule 4: app and Cloud should share primitives even if frameworks differ
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.
12. Migration strategy
Do not redesign everything at once. Use this sequence.
Phase 1: lock the foundations
- create canonical semantic tokens
- alias current app tokens to the new token names
- document primitive families and approved variants
Phase 2: unify the most reused primitives
Start with:
- Button
- Card
- Input
- Sidebar row
- Modal shell
These give the largest visual consistency gain.
Phase 3: unify shell patterns
Standardize:
- page background
- sidebar shell
- panel/card shell
- list row selection
- headers and section spacing
Phase 4: refactor high-traffic screens
Prioritize:
- workspace/session surfaces in
apps/app - Cloud dashboard shells in
ee/apps/den-web - share/package/connect flows in
apps/app
Phase 5: remove local style drift
As primitives stabilize:
- reduce repeated one-off class recipes
- replace raw gray classes in repeated patterns
- collapse duplicate card/button/input styles into primitives
13. Recommended initial source of truth files
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.
14. Recommended file plan for the next step
The smallest safe implementation path is:
Step A
Introduce canonical --ow-* aliases in apps/app/src/app/index.css without removing --dls-* yet.
Step B
Refactor Button, Card, and TextInput to consume shared semantic tokens.
Step C
Use the Den dashboard shell as the reference for:
- sidebar shell
- row selection
- neutral panel rhythm
Step D
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
15. What a successful system looks like
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
16. Anti-goals
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.
17. Immediate next recommendation
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.