Design System

L'eto Design System

A token, component and governance layer extracted from a live medical aesthetics and dental site I designed and built end to end.

Mo RezaeiVersion 1.0August 2026 6 components33 tokens
Role
Designer and developer
Tools
Figma, Webflow
Year
2026
Scope
Tokens, components, governance
01 · Context

A system underneath a finished site

L'eto Clinic is a doctor-led medical aesthetics and dental clinic in London. I designed and built the site end to end in Webflow, first as an original build and then as a full redesign that is the live product today.

Both versions were built intuitively. There was no formal design system underneath either of them, and for a single site built by one person that worked. It stopped working when a second sub-brand, L'eto Dental, launched on the same foundations.

So the system came after the build, not before it. I had a finished, working product and needed to find the rules it was already following, name them, and fix the places where it was quietly following none.


02 · The audit

Diagnosing my own work

I audited my own work, one version against the next, and then the live site against itself.

Drift is what happens to every product that grows. The point of an audit is not to find fault, it is to locate every place where a decision was implied but never recorded, and then record it.

Every figure in this section was measured from the live site using the browser's computed styles, not eyedropped from screenshots.

Buttons · no single source of truth for the primary action

The primary action, booking, is worded eight different ways across the site. These were collected from 16 of 129 indexed pages, so the true count is likely higher.

Contact us ×13 book now ×10 Book a Consultation Book online book free consultation Contact us now contact now Join Free, Start Earning

Capitalisation is inconsistent across the set: title case, sentence case and all-lowercase all appear for the same action.

On the home page alone that action is rendered as four different components. Reproduced here at their measured values:

Book a Consultation → Join Free, Start Earning → book free consultation Contact us

Three font sizes (10, 11, 12px), two weights (400, 600), two letter-spacings (3px, 5px), two radii (100px, 0px), and four different fill and border treatments. The fourth is set in the same size, weight, tracking and colour as the section label above it, so it carries no affordance distinguishing it from static text.

Colour · the values existed, the decisions did not

The site's own stylesheet already defined colour variables, but named for appearance rather than for the job they do. Measuring every colour actually in use surfaced four near-identical near-blacks, applied interchangeably.

#03060847 uses
#00000017 uses
#1313139 uses
#04080c1 use

And two near-identical golds, one used for text and one for fills, neither of them the same value:

#bd803ctext
#c89958fills
Contrast · the accent failed AA as body-sized text

The gold used for eyebrow labels measured 2.92:1 against the page background. WCAG AA requires 4.5:1 for normal text, and these are small letter-spaced capitals, so the large-text exemption does not apply.

2.92:1
Fails AA · live value #BD803C on the page background
Treatments
4.67:1
Passes AA · system value #92601F
Treatments
4.61:1
Passes AA · grey-400 #6E6B68

The corrected gold stays recognisably the brand gold while clearing the threshold. The corrected label grey clears AA at 4.61:1 while staying visibly lighter than body copy, so the hierarchy survives the fix.

Forms · the highest-cost gap

The contact form collects four fields and not one of them is programmatically labelled: no label element, no aria-label, no aria-describedby. The visible Name, Email and Message headings sit above the inputs without being connected to them, so each field's only accessible name is its placeholder, which clears the moment someone types. The message field still carries its unedited default, "Example Text".

There is no helper text, and no error colour appears anywhere in the measured palette, so the site carries no error state of its own and leaves validation to the browser. On a site where people enter contact details to book a medical consultation, that was the highest-cost gap I found.

One claim I had to withdraw. I went in expecting to find no focus treatment, and measured the opposite: the field border shifts on focus to 4.29:1 against the input, clearing the 3:1 that WCAG asks of a non-text indicator. It is in the audit because I measured it, not because I assumed it.

Typography

Montserrat throughout at 9–13px for labels, in weights 400, 500 and 600, with letter-spacing between 2px and 5px. Weight, colour and tracking vary section to section with no defined role system, so the same visual rank is expressed several different ways.

Spacing

Measuring padding, margin and gap across the live site surfaced a dominant 4pt rhythm · 16, 24, 36 and 100 account for most of the layout · which is not the 8pt grid I would have assumed. Genuine drift sits alongside it at 2, 6, 9, 10, 14, 15 and 18px.

Four screenshots of the site's primary buttons side by side: a cream filled pill, a black filled pill, a line of plain letter spaced text with no button shape around it, and white text on a dark block.
Figure 1. The four primary calls to action, photographed on the live pages with their measured values.
A screenshot of the words book free consultation set in wide letter spaced capitals on a plain background, with no outline, fill or underline around them.
Figure 2. The booking action with every non-text cue absent.
A screenshot of the word Treatments in pale gold capitals on a light grey background, sitting above a large dark heading that is far easier to read.
Figure 3. The accent gold against the page surface, measured.
A screenshot of the clinic contact form: Name, Email and Message headings above dark rounded input boxes containing only faint grey placeholder words, and a Submit button.
Figure 4. The contact form, with every field identified by placeholder alone.

03 · The system

Two tiers, built as Figma variables

Primitives are named for what they are and hold raw values. Semantics are named for what they do and hold no values of their own, each one aliased to a primitive. Components bind to semantics only.

A brand change touches one layer and cascades cleanly. Nothing downstream needs to know what colour it is actually getting.

Tier 3

Components

Bind to semantics only. Never to a primitive, never to a raw value.

Button · --surface-inverse Input · --border-control Eyebrow · --accent
Tier 2

Semantics

Named for the job. Hold no values of their own · each is an alias.

--text-primary → base/ink --accent → base/gold --feedback-error → base/error --border-subtle → base/grey-200 --border-control → base/grey-400
Tier 1

Primitives

Named for what they are. The only layer holding raw values.

base/ink
#010507
base/grey-700
#414343
base/grey-400
#6E6B68
base/grey-200
#D3D4D4
base/surface
#F0EFED
base/sand
#E4DFDC
base/white
#FFFFFF
base/gold
#92601F
base/error
#9B2222

Why two collections, not one

Primitives and semantics live in separate Figma collections, because Figma sets variable modes per collection and the multi-brand work needs modes on the semantic layer only. The accent should resolve differently for Clinic and Dental. The base ink should not.

Measured, not guessed

Every value was measured from the live site using the browser's computed styles rather than eyedropped from screenshots. That is what surfaced the 4pt spacing grid the site actually uses, and the radii it already had: 10px for small tiles, 22px for cards, fully rounded for buttons.


04 · Components

Six components, depth over breadth

Button, Icon Button, Treatment Card, Eyebrow, Input Field, Footer Link.

Chosen to cover every property type Figma offers rather than to reproduce every element on the site. Each specimen below is live: it is rendered from the same tokens documented above, not screenshotted.

Component

Button

Text property · 2 variants

The Button replaces eight wordings and four styles with one component carrying a text property, so the label is editable per instance and no one needs to draw a new button to change a word.

The governance rule sits alongside it: the booking action always reads "Book a Consultation". The component removes the technical excuse for inconsistency; the written rule removes the permission.

Component

Input Field

3 states
Default
We use this to confirm your appointment.
Focus
We use this to confirm your appointment.
Error
Enter an email address with a domain, like you@example.com

This is the component I would point at first. Its error state signals three ways at once: a red border, a warning icon, and a written message describing the problem. Around one in twelve men has some form of colour vision deficiency, for whom a red border alone communicates nothing.

A person reclining in a treatment chair receiving an intravenous drip, with a clinician standing behind them.
Wellness

IV Nutrient Therapy

A clinician-led infusion for hydration and recovery.

Component

Eyebrow

06 Specialties

11 Treatments

Bound to --accent or --text-muted, both AA-safe on the page surface.

Component

Footer Link

Book a Consultation

Contact

Hover state introduced. The live site's footer navigation had none.


05 · Accessibility

Specified, not reviewed afterwards

Accessibility is specified in the system rather than applied afterwards, so meeting it is the default rather than a review step.

The corrected label grey clears AA at 4.61:1 while staying visibly lighter than body copy, so the hierarchy survives the fix. The corrected gold clears at 4.67:1 and still reads as the brand gold.

Focus states use a thicker, darker border rather than a subtle tint.

Borders that define the edge of an interactive control bind to a different token from hairline dividers, because a divider and a control boundary have different contrast requirements. --border-subtle is grey-200 and carries no contrast obligation. --border-control is grey-400 and does.

One rule came out of measuring, and then proved itself twice. Grey-400 clears AA on the page surface at 4.61:1 but drops to 4.01:1 on the sand card fill. The accent gold does the same thing, clearing at 4.67:1 on the page and falling to 4.06:1 on sand.

So a token that is safe in one place is not automatically safe in another, and the system has to record which is which. Body text and eyebrows inside cards bind to grey-700, not to grey-400 or the accent.

I found the second half of that rule by auditing this page. The treatment card specimen further up originally used a gold eyebrow on the sand fill and failed at 4.06:1. The fix was a binding rule rather than a second gold, because adding a near-identical colour to solve one placement is the exact problem this system was built to remove.


06 · What the system changed

Extracted, or introduced

Most of the system was extracted from the live site. A smaller set of things the site did not have were introduced, and it matters which is which.

Changes the system made to the design as it existed on the live site.
ChangeType
Label grey darkenedCorrection for AA contrast
Accent gold darkened, #BD803C → #92601FCorrection for AA contrast
Two label greys consolidated to oneConsolidation
Four near-blacks consolidated to oneConsolidation
Two golds consolidated to oneConsolidation
Error colour and error stateIntroduced the site had none
Form helper and error messagingIntroduced the site had none
Hover state on footer navigationIntroduced
Spacing scale extended at small endExtension of measured values

Everything else, including all remaining colour values, type roles, spacing, radii and component structure, was measured from the live site rather than invented.


07 · Governance

Built, not designed from scratch

The purpose of the system is that a new surface can be built rather than designed from scratch.

L'eto Dental is the test case. It shares the clinic's structure, typography, spacing and component set, and differs in brand accent. In this system that difference is a single variable mode: the same token names resolve to different accent values per brand.

Components bind to the accent role, not to a colour, so nothing is duplicated, restyled or forked to serve the second brand.

Mode

Clinic

The same eyebrow and the same button, resolving the accent role to the clinic gold.

06 Specialties --accent → base/gold
Mode

Dental

Identical component instances. One mode switch, no duplication, no forked styles.

06 Specialties --accent → base/teal

The Dental accent shown here is indicative. Swap it for the final brand value and every component instance updates with it.


08 · Reflection

In my own words

I built this site twice before I built a system for it. That order was not a mistake, but it did have a cost.

Working alone, intuition scales further than people tend to admit. I could hold the whole site in my head, so I never had to write anything down. The system only became necessary when L'eto Dental arrived, and a second brand needed the same answers I had been keeping to myself.

Auditing my own work was the uncomfortable part and the useful one. It is easy to look at drift and see failure. What I actually found was a set of decisions I had made correctly and repeatedly without ever recording them. The 4pt rhythm was already there. The radii were already consistent. What was missing was not judgement, it was a written record of judgement.

The habit I am taking forward is measuring instead of remembering. I went into this audit certain the contact form had no focus treatment, and the measurement said otherwise: it clears the contrast threshold comfortably. Had I trusted my memory I would have published a confident, specific claim about my own work that was simply wrong. The same lesson arrived again when a token that passes on one surface turned out to fail on another, and again when I found the accent gold failing where I had assumed a grey was the culprit.

The system has not made me a better designer. It has made my decisions legible, to whoever comes next and to me.

Thank you for reading

Want to see the system itself?

The Figma file holds the full variable collections, the component library and the written governance rules.