One shared language for five healthcare products at 314e.
Built on an existing library, with the care spent on clinical
workflows, token architecture, and keeping five teams in sync.
The library sheet in Figma. Every component has its own artboard, its
own states and its own name — and that name is the same word the
engineer types in the editor.
Source material & project context
Captures come from the working Figma libraries and the shipped
products. Two disclosures: the data grid in §08 is AG Grid's own public
demo, recorded while we were evaluating it — it is not a Lego screen;
and a collaborator's username has been cropped out of the repository
capture in §09. Colour values shown here are read back from those
captures. No adoption or performance metrics are claimed, because none
were measured at the time.
01 / Where it starts
Five products. Five ways to say Save.
314e builds software for the unglamorous middle of a hospital — the
record room, the training desk, the internal hiring pipeline, the
integration layer. By 2020 there were five products in that
portfolio, five teams, and five release calendars that had never
once lined up.
None of them had done anything wrong. Each team had solved the same
problems in isolation and arrived at reasonable, incompatible
answers. A date picker in one product, a different date picker in
the next. A destructive action confirmed in a modal here and in a
popover there. Somewhere in the estate there were four spellings of
the same empty state.
The tell was never how it looked. It was the arithmetic underneath.
Every team paid to design a table, then paid again to build it,
then paid a third time to test it. A designer moving between
products lost a week to relearning. And the person who actually
carried the cost was the one nobody in the room could see: the
hospital staffer who used two of our products in the same shift and
had to hold two grammars in their head to do one job.
The business case is throughput, not beauty. A
system is worth building when the same decision is being made in
more than one place. Consistency is the visible symptom; the saving
is in decisions never made twice.
Healthcare compounds it. Clinical users are
trained once and then left alone with the software, often at hours
when nobody is available to ask. Every inconsistency between our
products was a small tax collected at exactly the wrong moment.
Product A
2px radius 13/500
Product B
Pill, teal 15/600
Product C
Outline, serif 14/400
Product D
Caps, tracked 11/700
Product E
Gradient, shadow 14/400
Reconstruction, not a screenshot. Five plausible renderings of
the same control, drawn to the kind of divergence the estate actually
had. Each one is defensible on its own. That is the problem — there is
no argument to win, only a decision nobody had the standing to make.
02 / The decision that shaped everything
We did not start from a blank canvas.
The seductive version of this project is a year of drawing: a
button, then an input, then a select, then a date picker, until you
have a library with your name on it. We had five products already in
production and a team of five designers. That version of the
project would have finished in 2024 and shipped nothing in the
meantime.
So Lego was built on top of an existing enterprise library rather
than beside one. Ant Design gave us the substrate: React
components, a component taxonomy already argued over by people with
more data than us, and — the part that actually decided it — the
handful of controls that no sane team builds from scratch. A
Transfer. A Cascader. A
TreeSelect. A table with column pinning and grouping.
Those are not components. They are small products.
What that bought was about a year. What it cost is less often
admitted: when you adopt a substrate, you inherit a stranger's
opinions. Their density. Their default blue. Their idea of how much
air a form needs. The first real design job on Lego was not drawing
anything — it was auditing decisions we had not made, and deciding
which ones we were going to live with.
What we kept. The component taxonomy, the
interaction behaviour, the accessibility groundwork, and every
control whose complexity is in its edge cases rather than its
surface.
What we overrode. Colour, type, density, iconography,
and the semantics of the primary action — everything a user reads
as “this is our software”, and nothing a user reads as “this is how
a tree select behaves”.
What that implies. The system's own opinions had to
live in tokens, not in forked components. A fork is a debt with
interest; a token is a value you can change on a Tuesday.
A design system is not a drawing exercise. It is a set of decisions
about which decisions are no longer yours.
03 / Token architecture
One tier says what exists. One says what it means here.
Colour was where the system either held or came apart, so it got
split in two before anything was drawn.
The system tier is a closed vocabulary: the base ramps, the
neutral ramp, and the data-visualisation set. It answers one
question — which colours exist at all — and it is deliberately
boring. Nothing in it means anything yet.
The product tier is where meaning gets assigned.
colorPrimary is not a colour; it is a pointer to one
step in a system ramp. Which step, and therefore what “primary”
looks like, is a product's decision to make.
That indirection is the whole architecture, and it earns its keep
the moment two products disagree. Jeeves resolves its primary to
blue. Penknife resolves to violet. Neither forked a component to do
it; both are the same Button, reading a different
value from the same slot.
Why not just theme it? Because “theme” usually
means a stylesheet that overrides the library, and stylesheets
cannot be reasoned about. A token table can: you can diff it,
review it, and hand it to an engineer without a conversation.
The test we used. If changing a colour requires
opening a component file, the architecture has already failed —
it just hasn't told you yet.
Tier one
The system palette
Base ramps, neutrals, and the data-visualisation set. Defines the
colours that exist. Assigns no meaning, and is not allowed to.
Tier two
The product palette
Binds roles — primary, success, warning, error, navigation — to
steps in tier one, according to what the product does and who is
using it at 3 a.m.
1
2
3
4
5
6
7
8
The primary ramp, tier one. Step 6 is the base; every other
step is a derivation of it, which is why a product can move its
primary one notch without redrawing anything. Values read back from
the Figma variables capture below.
Tier one · rawColors/Base/Blue/6#1677FF
Tier two · rolecolorPrimary→ Blue/6
Component · propertycolorPrimaryHover→ Blue/5
Renders as
Three hops, and only the first one holds a literal value. An engineer
changing colorPrimary changes every hover, border, focus
ring and disabled state that derives from it — which is the point, and
also the risk.
The variable table doing the work. Every row on the right resolves to
Primary/primary-n, not to a hex — so the entire
informational state of the system is seven bindings, and moving the
ramp moves all of them at once.
04 / Typography
Ten sizes, and a leading that is a constant, not a ratio.
Inter, for reasons that are mostly unromantic: open licence, a real
range of weights, unambiguous figures, and a tall x-height that
survives being set at 12px in a dense table by someone reading it
at arm's length.
The base size is 14px, and it is 14px for a stated reason rather
than a habit. At a typical seated distance of 50cm, a 14px cap
subtends roughly a third of a degree — which is comfortably above
the threshold where sustained reading starts costing effort. Fix
the base, and the rest of the scale has something to be derived
from.
The detail worth stealing is in the second column. Line height is
not a multiplier — it is the font size plus exactly eight pixels, at
every single step. Which means the effective ratio slides from
1.67 at 12px down to 1.12 at 68px, all on its own.
That is the correct direction, and a fixed ratio gets it backwards.
Small text set in long lines needs air to keep the eye on the right
row; a 68px headline set with 1.5 leading falls apart into
unrelated stripes. A constant does the optical thing automatically,
and it has the side benefit of being arithmetic a developer can
hold in their head.
Why it matters more here. Clinical screens are
dense by necessity — a record is not a marketing page. Most of the
estate lives in the bottom three steps of this scale, so the small
end had to be the well-designed end.
The rule we wrote down. Steps are not decoration.
If a screen needs a size that is not on this list, the screen is
wrong before the scale is.
SizeLine heightSpecimen
68px76 · +8Discharge summary
56px64 · +8Discharge summary
46px54 · +8Discharge summary
38px46 · +8Discharge summary
30px38 · +8Discharge summary
24px32 · +8Discharge summary, signed
20px28 · +8Discharge summary, signed 14:20
16px24 · +8Discharge summary, signed 14:20 by A. Rao
14px22 · +8Discharge summary, signed 14:20 by A. Rao — base size
12px20 · +8Discharge summary, signed 14:20 by A. Rao — table and caption size
Rendered live in Inter at the real values, not screenshotted. Read the
middle column downward: the gap never changes.
05 / The library
Fifty-four components, in five families.
The inventory below is the whole of Lego's surface area. It is
worth reading as a list, because the shape of it is the argument:
almost every entry is a solved problem somewhere else, and the
value we added was in the tailoring, the naming and the refusal to
extend it.
Eight of them are marked. Those are the ones the build notes single
out as re-specified for clinical work — the transfer, the complex
lists, the tooled navigation, the data tables, the heavy modals,
the forms. They are also, not coincidentally, the components where
Dexit, Muspell and Jeeves diverge most: a document queue, a patient
record and a training asset library ask very different things of
the same table.
The rest were adopted, re-tokened and left alone. That was a policy,
not laziness. Every component you take ownership of is one you
maintain forever, and a five-person team that owns fifty-four
components owns none of them well.
Naming was half the battle. The name in Figma, the
name in Storybook and the name in the import statement are the same
word. Three vocabularies for one thing is how systems quietly stop
being used.
Every variant is a promise. A button with five
types, three sizes, an icon slot, a ghost mode and four states is
already 200-odd renderings that have to hold. Adding a sixth type
is never one decision.
General 3
Icons
Button
Divider
Navigation 7
Affix
Breadcrumb
Dropdown
Menu
PageHeader
Pagination
Steps
Data entry 17
AutoComplete
Cascader
Checkbox
DatePicker
Form
Input
InputNumber
Mentions
Radio
Rate
Select
Slider
Switch
TimePicker
Transfer
TreeSelect
Upload
Data display 18
Avatar
Badge
Calendar
Card
Carousel
Collapse
Comment
Descriptions
Empty
List
Popover
Statistic
Table
Tabs
Tag
Timeline
Tooltip
Tree
Feedback 9
Alert
Drawer
Message
Modal
Notification
Popconfirm
Progress
Result
Skeleton
54components across five families● marked entries were re-specified for clinical use
The button, exhaustively. Type × size × icon × ghost × state,
drawn out rather than described. Matrices like this are tedious to
build and are the only honest way to find the combinations that do
not work — a ghost dashed button in a disabled state has to be
somebody's problem before it is a user's.
Button — rendered live from Lego's tokens
Inter · 32px control height
Data entry — seventeen components, the largest family and the one clinical work leans on hardest.Feedback — nine components, every one of which has to work without colour. See §07.
06 / Dark mode
Not a preference. A night shift.
Dark mode arrives in most product roadmaps as a taste question. In a
hospital it is a working condition. Wards run dim at night so people
can sleep; the staff working through those hours are dark-adapted,
and a full-white record screen at 3 a.m. is a flashbulb that costs
them several seconds of usable vision every time they open it.
What made it affordable was the decision made three chapters
earlier. Because no component held a literal colour, dark mode was
not a re-skin — it was a second column in the variable table. Every
row already existed. Each one just needed a value for the other
lighting condition.
The one thing that does not survive the flip is the ramp itself.
Inverting a palette does not work: a blue tuned to sit on white is
too saturated and too bright against near-black, so the dark column
is its own set of steps, chosen to hold the same
relationships rather than the same values. Same role, same
contrast behaviour, different number.
Try it. The switch below drives every specimen on
this page. It is doing exactly what the system does — swapping one
set of token values, touching no component.
Where it still leaks. Charts and third-party
surfaces. Anything the system does not own has to be themed
separately, and that is the honest cost of §08.
The same components, one token column apart
Pending reviewSignedAmendedOverdue
This record has an unsigned amendment.
The icon carries the state as well as the colour — see §07.
Assign reviewer
Working days only.
Notify on sign-off
Review queue
Record
Department
Status
Days open
Discharge summary
Cardiology
Signed
1
Operative note
Orthopaedics
In review
3
Consent form
Day surgery
Amended
6
Referral letter
Neurology
Overdue
11
Live components, not images. Flipping the switch rewrites nineteen
custom properties; every control above re-renders from them without a
single rule of its own changing.
07 / Accessibility
Colour is an indicator. Never the message.
Lego targets WCAG 2.1 AA, which in practice comes down to three
habits: contrast that clears the bar, a keyboard path to everything,
and semantics that survive being read aloud rather than looked at.
The contrast table below is computed in your browser, live, from the
same palette the specimens use. It is worth publishing unedited,
because the interesting rows are the ones that do not pass
everything.
Read the semantic set — success, warning, error. Against white,
not one of them clears 4.5:1, and only the error red clears 3:1 at
all. That is not a flaw to be fixed by nudging the hex; it is the
nature of a saturated mid-tone. It tells you what those colours are
allowed to be. They can fill a shape, tint a background, or carry a
border. They cannot be the text, and they cannot be the only thing
distinguishing one state from another.
Which is why every component in the feedback family has an icon
slot, and why showIcon is a documented property rather
than a decoration. The variant sheet below draws both branches on
purpose. A user who cannot separate the red from the green still
gets a cross and a tick, and the state still reaches them.
Keyboard. Every interactive control is reachable
and operable without a pointer, with a visible focus ring that is
not the browser default and not removed.
Screen readers. State lives in ARIA, not in styling.
A tag that means “overdue” says so; it does not merely turn red and
hope.
The unglamorous one. Inter's figures are
unambiguous at 12px. In a system where numbers are doses and dates,
that is an accessibility decision wearing a typography hat.
Contrast ratios, computed live against #FFFFFF. Thresholds: 4.5:1 body text, 3:1 large text and UI boundaries.
Token
Value
Ratio
Body 4.5:1
Large / UI 3:1
So it may be
Both branches, drawn. Info, error and success, crossed against
showIcon and showButtons. The right-hand
column is the reason the property exists: with the icon suppressed,
colour is doing the work alone, and you can see for yourself how
little is left.
08 / Dependencies
A system is defined by where it stops.
Four things sit inside our products that Lego does not own, and
deciding that was as much design work as anything in the library.
The temptation, always, is to absorb them. A charting component
feels like it belongs in a design system; so does a rich text
editor. Both are traps. A production-grade data grid with
virtualisation, column grouping, pinning and export is a multi-year
product with a company behind it. Rendering one is not the hard
part. Keeping it correct for a decade is.
So the boundary was drawn explicitly, and the system's job at each
edge changed from building to binding: take the
dependency, feed it Lego's tokens, wrap it in Lego's spacing, and
make it answer to the same names as everything else. A user should
not be able to feel the seam. An engineer should always be able to.
The cost, stated plainly. Four dependencies are
four theming surfaces the token flip has to reach separately, and
four upgrade paths that are not ours to schedule. That is the bill
for not spending a decade building grids.
Tables
AG Grid
Virtualised rows, column groups, pinning, grouping and export. The
single largest thing we chose not to build.
Charts
AntV
Statistical charts through G2, relationship and network graphs
through G6, and flow, DAG and ER diagrams through X6 — the estate
needed all three shapes.
Rich text
Lexical
Selection, composition and IME behaviour in a content-editable
surface. Correctness here is measured in years of edge cases.
Query building
React Query Builder
Nested boolean rule construction, themeable enough to accept Lego's
controls rather than imposing its own.
AG Grid's own public demo, captured during evaluation — not a Lego
screen, and the sample data is theirs. It is here to show the
scope of what was being adopted: grouped headers, a filter row, pinned
columns and a live column picker. That is the thing a five-person team
would have spent a year approximating badly.
09 / Design to code
A library nobody imports is a mood board.
The failure mode for a design system is not that it is badly drawn.
It is that it is beautifully drawn, published, admired, and then
quietly not used, while five teams carry on shipping whatever they
were shipping before.
So the second half of the work was distribution. Lego ships as React
and Sass. Figma holds the source of truth for design; Storybook
renders the built components; Git carries both, and every spec
change and every code change lands as a reviewable diff. Four
front-end engineers were on the project specifically to keep that
chain connected — not to implement screens, but to make sure the
thing designers changed and the thing engineers imported never
drifted apart.
The rest is unglamorous plumbing that decides everything. One
vocabulary across Figma, Storybook and the import statement. A Figma
template preloaded with the styles and the library, so the cheapest
path to a new screen is the correct one. Published libraries, so an
update propagates instead of being announced.
Designers on the team learned enough CSS and JS to read what shipped
rather than only what was drawn. That sounds like a nice-to-have. It
is the difference between a handoff and a conversation.
Make the correct path the lazy path. Adoption is
never won by advocacy. It is won by being the fastest way to start.
The weak link, honestly. Reference documentation
lived in Google Docs, with a zeroheight site alongside it. It was
collaborative and everyone could reach it — and it was
unversioned, sitting next to a library that was versioned properly.
Documentation that cannot tell you which release it describes will
eventually lie to somebody.
Source of truth
Figma
Variables, styles and the component library. Published, so changes propagate rather than get announced.
Built reference
Storybook
Every component and every state, rendered from the real code. The place a disagreement gets settled.
Distribution
React + Sass, in Git
One package, reviewable diffs, shared naming. The import statement is the same word as the Figma layer.
Reference
Docs
Usage, rationale and do-not-do. Collaborative, accessible, and — the flaw we would fix — unversioned.
The other half of a design system. A file tree of feature
folders, and beside it the shared editor configuration — linting,
formatting, spell-checking — committed to the repository so the
conventions apply to everyone by default rather than by reminder. A
collaborator's username has been cropped from this capture.
10 / What it came to
Five products, one grammar.
Lego went into Dexit, Muspell, Jeeves, Penknife and Zsegment, and it
is still in them. Products built years apart now open with the same
controls, the same spacing and the same words for the same things,
and a designer moving between them starts on the second day rather
than the second week.
We did not instrument any of that, and it would be easy to write a
number here that nobody could check. What can be said plainly is
structural: no product forked a component to change its colour; dark
mode shipped as a token column rather than a project; and the
inventory has stayed at roughly its original size, which for a
design system is the harder achievement.
Two of the products Lego serves have their own case studies on this
site. It is worth reading one of them
Jeeves
or
Penknife
with this page in mind: everything in them is drawn in this
vocabulary.
Five products. Fifty-four components. Two years, and one
vocabulary left behind.
Bring forward
Buy the substrate. Spend the year on the delta.
The value we added was never in re-deriving a select menu. It was in
the eight components clinical work actually bends, the token
architecture, and the naming. Adopting a base library was the
decision that made the rest of it possible, and it is the first
thing I would do again.
Keep challenging
Indirection is a loaded weapon.
One token feeding four hundred surfaces is the architecture working
and the risk in the same sentence. A one-step change to a ramp can
quietly break a contrast ratio three components away. We caught
those by eye. A system this leveraged should be catching them in
CI.
Develop next
Version the documentation like the code.
The library had releases; the documentation did not. That gap is
where a system starts telling people things that used to be true.
Docs belong in the repository, built from the same source, shipped
with the same version number.