Work Every visual dimension is a mode.
Theme
Mode
Text size

1 component · 0 forks
Policy · Health plan

Claim progress

color/accent_action/bg/based

color/accent_action/text/invert

ColorGlobal blueLifestyle blue
FontGlobal familyLifestyle family
RadiusRounded cornersFully round
Thai Life InsuranceDesign foundationJan – Mar 2025

Every visual dimension is a mode.

Thai Life needed several brands, two themes (Global and Lifestyle), light and dark, and adjustable type and radius. Done the usual way, that's a component for every combination.

My role
Led the design system build, in three months
When
Jan – Mar 2025, onsite
Team
3 designers, 6–7 frontend developers, 2 QA engineers
Built
Color, type, spacing, sizing, radius and shadow tokens; full component library
Tools
Figma variables and modes

Impact

5visual dimensions controlled by modes
8semantic color groups
0forked components

Try the switches above. The ยืนยัน (Confirm) button is one component: the theme changes its color, font and radius together, and every example below follows. Colors are close to production, adjusted for contrast; exact values stay confidential. Fonts are open stand-ins.

Three months was enough because I had done this before

  • On ttb's v2 I watched tokens that were too specific turn every new need into a new variant. At Thai Life, every token was named by intent from day one.
  • On v3 I had already made each brand a Figma variable mode. Thai Life pushed the idea to theme, light and dark, type and radius.
  • Working beside iOS, Android and web developers at ttb shaped how I set up names, specs and handover here.

The ttb story is in two parts: v2, nine squads and one way to build a screen, and v3, two brands with one component library.

The question that made it hard

Can one token system cover two themes, light and dark, several products, three font families and variable shape, without duplicating a single component?The brief I set myself

My answer: every visual dimension is a variable mode. Components stay identical and resolve per context.

Diagram from the TLI Foundation docs: the same confirm button in theme Global, theme Lifestyle and theme Global in dark mode. Each reads color/accent_action/bg/based for its background and color/accent_action/text/invert for its label. Global is a deep blue with small rounded corners, Lifestyle a brighter blue and fully round, Global dark a lighter blue on a dark background.
From the foundation docs: one button, two token names, three results

What's inside

Foundations

Mode hierarchy

brand/→Primary & secondary palette→color/→Semantic tokens→Global · Lifestyle→Light · Dark

Each brand color is generated into shades and tints in 10% steps, named like brand/primary/brand_red_s100 for a 10% shade.

s900 000 · base t900
Shades (s) add black, tints (t) add white. Representative color.

Tokens grouped by what they control

The tokens are split by what they control, so each collection only changes what it owns.

Colorbrand → color

The brand palette in shade and tint steps, then the semantic tokens named by intent. color switches by theme (Global, Lifestyle) and by light or dark.

Typographytext_control → brand_typography → text_style

Size and line height per scale, then families and weights, then the published styles.

Sizingsizing

One scale for padding, gap and radius.

Shadowshadow

Elevation from shadow_base to shadow_lv4, and shadow_up for elements at the bottom of the screen.

Token naming

colorPrefix/accent_actionGroup/bgElement/invertVariant_on_hoverState
One token, read part by part: the inverted CTA background, on hover
PartRequiredValues
PrefixYescolor/
GroupYesneutral, accent, accent_action, success, error, warning, information, infographic
ElementYesbg, text, icon, border
Level / priorityVariesbased, lv1–lv4, primary, secondary, tertiary
VariantOptionalsubtle, soft, invert, highlight
StateOptionaldisable, on_press, on_hover
Examples: color/accent_action/bg/based (CTA background) · color/error/border/primary
Figma Variables panel, color_system collection. The collection list on the left includes brand_color, color_system, shadow, sizing, text_style, theme_global and theme_lifestyle. The table shows neutral background tokens such as highlight, lv1 to lv4 and disable_soft, each pointing to a theme token, in a Global column and a Lifestyle column.
In Figma: the color_system collection. Every semantic token points to a theme token, with one column per theme.

Eight color groups, one switch

Every group has the same parts, so one mode change restyles them all. Try it on the tiles below.

neutral
accent
accent_action
success
error
warning
information
infographic
Each group's base background with its inverted text. Contrast is measured live in your browser.
The Token Color page of the TLI Foundation docs: columns for neutral, action accent, accent, information, error and infographic, each with swatch lists for Lifestyle light, Global light and Global dark.
The Token Color page in the docs: every group listed for Lifestyle light, Global light and Global dark

Tokens on a real screen

The docs show the tokens on a sign-in screen, rebuilt here with representative colors. Each part reads one token.

1Pagecolor/neutral/bg/based
2Containercolor/neutral/bg/lv1
3Headingcolor/neutral/text/primary
4Supporting textcolor/neutral/text/secondary
5Error bordercolor/error/border/primary
6Buttoncolor/accent_action/bg/based
…/text/invert
Sample screen. The structure follows the docs; copy and colors are placeholders.

Background elevation

Backgrounds layer from based through lv1–lv4 to hl. The rule: lv2 can only sit above lv1. Shadows climb the same way, from shadow_base to shadow_lv4, with a shadow_up set for sticky footers and bottom sheets.

shadow_baseBackground elements
shadow_lv1Buttons, floating elements
shadow_lv2Modals
shadow_lv3Dropdowns, tooltips, context menus
shadow_lv4Overlays, floating panels, popups
shadow_up_*Sticky footers, bottom sheets
A dropdown popover uses lv2 because it sits on an lv1 container. In dark mode the levels also get lighter as they rise.
Shadow diagram from the TLI Foundation docs: five stacked layers on a 3D grid, labelled shadow_base to shadow_lv4.
Shadow levels in the foundation docs
Figma Variables panel, theme_global collection. Neutral background tokens such as lv1_highlight, darker, based and lv1 to lv4, each with a light value and a dark value taken from the palette.
In Figma: the theme_global collection. Each background level has a light and a dark value. Palette names are generalised.
Foundations

Typography in three layers

1 · PrivateText control

Font size and line height per scale (S, M, L).

2 · PrivateBrand typography

Three families (display, heading, body), four weights.

3 · PublicText styles

Published styles built from layers 1 and 2, switchable by theme.

Text size follows the phone's setting: S, M and L match iOS Text Size and Android Font Size. Line height is 120% for two families and 180% for the third, which Global uses for all text under 18px.

Use the Text size switch (S, M, L) to scale every style at once.

heading_2xl

สวัสดี Hello Thailife

heading_xl

สวัสดี Hello Thailife

heading_l

สวัสดี Hello Thailife

heading_m

สวัสดี Hello Thailife

body_l

สวัสดี Hello Thailife

body_m

สวัสดี Hello Thailife

label_l_strong

สวัสดี Hello Thailife

metadata

สวัสดี Hello Thailife

Some of the published text styles. Sizes are representative; the fonts are open stand-ins that change with the theme.
Part of the text styles table from the TLI Foundation docs: each style name, such as label_m and label_xl_strong, rendered in a Global column and a Lifestyle column with a different font.
Text styles in the docs: one style name, a different font per theme
Figma Variables panel, text_style collection. Styles such as label_s and label_s_strong store font family, weight, size, line height and decoration as variables, in a Global column and a Lifestyle column.
In Figma: text styles stored as variables. One style name, a different font per theme. Font names are generalised.
Foundations

One scale for padding, gap and radius

Padding, gap and radius all read from one sizing scale, mostly in 4px steps, plus 1, 2 and 6.

Radius: none, small buttons, cards and inputs, main containers, avatars.

The sizing scale. Grey bars are above the recommended 40px for padding and gap.

Radius stops at sizing.24, padding and gap at sizing.40 (a hero can go further). Inside a component, the gap sits two steps below the padding: 16px padding, 8px gap.

Radius belongs to the theme too: small rounded corners in Global, fully round in Lifestyle.

Components

Components in three groups

Foundation

Button, Input, Checkbox, Radio, Toggle, Select, Badge, Avatar

Layout

Card, Container, Divider, Grid, Stack, Drawer, Modal

Feedback

Alert, Toast, Tooltip, Progress, Skeleton, Loading

Each component is built once and reads its values from the collections, so switching a mode restyles all of them.

States as the component specs list them, drawn with representative colors. They follow the switcher too.
Handover

Handing over to 6–7 developers

With 6–7 frontend developers, the handover had to leave nothing to interpret.

  • Token names agreed with the developers, so a name in Figma is the same name in code.
  • A spec for each component that lists every state: default, hover, focus, disabled and error.
  • Walkthroughs of the Figma file and its variables, with questions.
  • Governance docs: how to contribute, the naming guide, how to deprecate a component, and the version policy.

What I learned

The goal isn't reusable components. It's a configurable design foundation.From my notes on the project

Get the token architecture right and a new brand is configuration, with no rebuild. If a component exists twice, the token model is wrong.

Next caseTwo brands, one component library.