Design System · PeopleDesk v1.0
A design system for numbers
people have to trust.
PeopleDesk is a white-label HR, payroll and finance platform. Its system is built around one question: can the person running payroll tell, from the interface alone, what they are allowed to see, what will stop them, and what cannot be undone?
Context
Built while designing. Written down after.
I designed PeopleDesk end to end and built its component library in Figma as the screens took shape. The library worked, but its rules lived only in my head: why a contractor badge is amber, why one alert blocks a payroll run and another does not, why a hidden field shows a lock instead of a blank.
A white-label product is handed to teams that will never talk to its designer, so I turned that library into a documented system. This page walks through it the way a reseller's team would meet it: foundations first, then behaviour, then the components, then the rules that hold it together.
Every specimen below is live code built from the system's own tokens.
01Tokens
Three tiers. Ten values to rebrand.
Primitives hold raw values, semantic tokens give them a job, and component tokens scope that job to one element. Components may only reference the second and third tier. A hard-coded hex or a primitive inside a component is a review blocker.
--pd-violet-500A raw value with no meaning attached. Owned by the system.
--pd-action-primaryA role in the interface. The only tier a reseller may repoint.
--pd-btn-primary-bgScoped to one component, so it can be retuned without touching the rest.
Repoint, don't repaint
A reseller swaps the ten --pd-violet-* primitives and the logo mark. Every action, focus ring and brand badge follows, including the translucent states, which are mixed from violet-500 instead of written as fixed values.
Status never moves
Green, amber, red and blue stay fixed in every tenant. In the preview the Ready and No hours badges keep their colour, because green has to mean success no matter whose logo is on top.
Named by job, not by pixel
The grammar is --pd-{family}-{role}-{variant}. --pd-status-critical-bg is readable without the file open. --pd-red-light-2 breaks the moment the hue changes.
02Color
Dark by default. Color never works alone.
Depth comes from surface steps, not shadows, because on a dark canvas a shadow reads as a smudge. Violet is reserved for action and brand. Status colours always travel with an icon and a text label, so no meaning depends on hue alone.
| Token | Value | Used for | |
|---|---|---|---|
surface-canvas | #0F0F11 | The page. Never pure black | |
surface-raised | #17171A | Cards, tables, alert rows | |
surface-overlay | #1F1F24 | Modals and menus above the page | |
text-primary | #F0EEEA | Body copy and figures. Warm, not white | |
text-secondary | #9896A0 | Meta lines and helper text | |
action-primary | violet-500 | The one primary action per view | |
action-danger | red-500 | Destructive actions only | |
surface-active | violet-500 · 14% | Selected nav item and row |
03Typography
One family. Numbers that line up.
Inter across the whole product, in ten steps. Most of the interface lives between 11 and 14 pixels, because HR and finance users scan dozens of rows per session. Anything above 20 is a page title or a figure meant to be read from across a desk.
Money is always tabular
Currency and counts use tabular-nums, right-aligned. The decimal points stack, so a scan down the column is enough to spot an outlier, and a changing digit never shifts the layout.
Large figures are tightened
Display and title sizes carry -0.02em tracking so a six-figure total reads as one number instead of a row of drifting digits.
04Spacing & Layout
4px base. Dense on purpose.
An 8px rhythm on a 4px base. The two smallest steps exist only for icon-to-label gaps and badge padding. Whitespace is spent separating groups, not padding individual items, because the people using this are operators, not visitors.
space-24pxspace-38pxrow-pad11pxspace-412pxspace-516pxspace-724pxspace-832pxspace-1048pxspace-1164pxspace-1280pxThe one off-grid value. Table rows use 11px vertical padding to hold a 42px row at 13px type. It is tokenised so it cannot spread by accident.
Tables reflow, they never scroll sideways
Below 1100px the payroll table becomes one card per employee. Horizontal scroll would hide the status column, which is the column that blocks a run.
Never truncate amount or status
Project and role may wrap. The amount and the status are the reason the table exists, so they are always shown in full.
Radius shows nesting
Badges 3px, controls 6px, cards 12px, modals 20px. A child is always one step less round than its parent, never equal.
05Behaviour
Severity has consequences. Friction is spent once.
A problem's severity is defined by what happens if it is ignored, not by how serious it sounds. Each of the three severities maps to a different interaction, and only one action in the payroll flow earns a confirmation gate.
Resolve, review, consent, receipt
Only dispatching payroll and deleting an employee record cannot be undone. Both follow the same four steps, and nothing else in the product may use that shape.
Typing is the consent
The gate input has a dashed critical border so it never feels routine. The button stays disabled until the figure matches. The escape route is "Back to review", never "Cancel", so nothing entered is lost.
Motion confirms, never decorates
Five durations from 80 to 320ms. The slowest is reserved for the payroll success state, the one place a pause is wanted. Under reduced motion every duration collapses to zero.
06Permissions
Same record. Four outcomes.
The defining pattern of PeopleDesk. Permissions apply per field, not per page, so the same employee record renders differently for each role. Which outcome a field gets is a design decision, documented so it is never left to whoever writes the query.
Rendered normally. The role owns or needs the field.
Rendered with controls removed and a read-only label.
Replaced by a lock note naming who can see it.
Not rendered at all. The role should not know it exists.
Silence reads as broken
An empty panel tells a finance user the record is incomplete. A lock note tells them the data exists, who can see it, and that nothing is wrong.
Withheld or omitted is a design call
Withhold when the role may know the field exists. Omit when it may not. The consultant never learns that compensation or reviews exist on this record.
The lens is always on screen
Every record header says which role it is being viewed as. A user with more than one role should know which lens they are looking through before they act on what they see.
07Components
Seventeen components. Each one carries a rule.
Every component is documented with its anatomy, states, tier-3 tokens, accessibility notes and at least one do and don't. Variants are named by role, never by appearance: --critical, not --red.
Controls
5 components
- Button 5 variants, loading
- Input & form field help, error
- Segmented control 2 to 4 options
- Badge type vs status
- Avatar 5 sizes
Structure & data
7 components
- Nav item & sidebar hidden vs disabled
- Page header
- Stat card
- Data table reflows to cards
- Kanban & candidate card
- Request row approve, reject
- Charts 5 viz tokens
Feedback
5 components
- Alert row 3 severities
- Progress
- Banner & lock note
- Modal & confirm gate used once
- Empty, loading & error
08In the product
Where the system shows up.
The same tokens and components, assembled. The payroll dashboard leans on tabular numerals, status badges and the resolver. The employee record is where the permission outcomes and the lens live.
09Voice
A competent colleague. Never cheerful about money.
Microcopy is part of the system, with the same do and don't treatment as a component. Name the person and the problem in the same line. Say what succeeded before what failed. No exclamation marks anywhere in the payroll domain.
10Principles
The rules behind the system.
Four rules settle most arguments in this product. When a component decision is contested, the earlier principle wins.
Severity is structural, not decorative
Every status pairs a hue with an icon and a label, and each severity maps to a different consequence. No fourth severity without a behaviour of its own.
Friction is spent, not spread
The product is fast everywhere except where an action cannot be undone. Adding confirmation to a reversible action devalues the gate that matters.
Permission is visible, not silent
When a role cannot see a field, the product says so. A user should understand that data is withheld, not wonder whether the record is incomplete.
Density serves the operator
Row height, type size and spacing are tuned for scanning, not comfort. Whitespace separates groups instead of padding individual items.
Deliberately left out. No light theme: the tokens are namespaced so one can be added, but a second mode would have doubled what needs maintaining. No custom icon set: Lucide outline, with reserved meanings, so the lock always means withheld by permission and never "secure".
What this page does not claim. PeopleDesk was built to be resold, so there is no adoption or reuse data behind this system yet. With a reseller shipping on it, I would track hard-coded values introduced instead of tokens, and how long a rebrand takes against the ten values it should touch.