← Design System← Design System
Design SystemDesign System19 Th7, 2026Jul 19, 202622 phút đọc18 min read

Tooling & Quy trình Design-to-DevTooling & Design-to-Dev Workflow

Thuộc bộ kiến thức Design System Roadmap.

Tổng quan

Mỗi lần hand-off giữa designer và engineer là một cơ hội để mọi thứ trôi dạt (drift) khỏi nhau. Không có vocabulary chung, designer vẽ một button với padding 12px và màu fill #3B82F6; engineer lại build nó với 10px và #3B72F5 vì đó là giá trị dùng ở ticket trước. Nhân con số đó lên hàng trăm màn hình và hàng chục engineer, sản phẩm sẽ dần rã ra thành hàng nghìn component gần-giống-nhau nhưng sai lệch chút ít. Đây không phải vấn đề về kỷ luật làm việc — mà là vấn đề về tooling. Không ai có thể nhớ chính xác “thang spacing đúng” trong đầu khi làm việc với hàng trăm file.

Design system giải quyết việc này bằng cách biến bản thân hệ thống thành nguồn chân lý (source of truth), và tooling chính là thứ giữ cho design và code luôn trỏ về cùng một nguồn đó thay vì trôi dạt thành hai thực tại song song. Bài này bao quát toolchain cụ thể giúp điều đó khả thi: design editor (gần như luôn là Figma ở thời điểm hiện tại) và các plugin biến nó từ công cụ vẽ thành công cụ nhận biết token; các định dạng interchange (chủ yếu là JSON) giúp token chảy qua lại giữa design và code; UI kit phản chiếu chính xác thư viện component đã code trong Figma; Storybook như workbench nơi component thực sự được build và document bằng code; Style Dictionary như pipeline biến một nguồn token duy nhất thành định dạng gốc của mọi nền tảng; và quyết định về tech stack (framework-specific hay framework-agnostic) — quyết định một thư viện component code có thể vươn xa tới đâu trong tổ chức.

Không công cụ nào trong số này design system — chúng là hệ thống ống nước (plumbing) giúp design system trở thành thứ vận hành thực sự thay vì một file PDF hướng dẫn tĩnh mà chẳng ai tuân theo.

Kiến thức nền tảng

Vấn đề hand-off, nói chính xác

“Hand-off design-to-dev” theo cách truyền thống nghĩa là: designer hoàn thành một file Figma, engineer mở file đó lên, và engineer đọc thủ công các giá trị — màu sắc, spacing, font size, corner radius — rồi tự tay mã hóa lại chúng thành CSS hoặc một object StyleSheet. Mỗi lần đọc như vậy là một cơ hội để xảy ra sai số làm tròn, click nhầm, hoặc một xấp xỉ “gần đúng là được”. Tệ hơn, hai artifact (file Figma và codebase) không có liên kết cấu trúc nào với nhau: nếu designer sprint sau chỉnh lại một token spacing, không có gì báo cho engineer biết component của họ giờ đã lỗi thời.

Một toolchain trưởng thành cho design system sẽ loại bỏ càng nhiều bước mã hóa lại thủ công này càng tốt:

Bước hand-off thủ côngThay thế bằng tooling
Designer chọn màu bằng mắt, engineer đọc mã hex bằng ước lượng từ inspector của FigmaCả hai cùng tham chiếu một token đặt tên (color.brand.primary) xuất ra từ một nguồn duy nhất
Engineer đoán spacing bằng thước đoToken spacing (space.4 = 16px) được chia sẻ qua output của Style Dictionary
Designer vẽ lại button từ đầu cho một màn hình mớiDesigner kéo một instance của component Button từ thư viện Figma chung, với cùng các variant như component đã code
Engineer build component, hy vọng nó khớp ý đồ thiết kếEngineer build dựa trên Storybook, designer review chính instance Storybook đã deploy thay vì một mock tĩnh
Hỏi trên Slack “component này đã có ai làm chưa?”Analytics về usage của component/token cho thấy cái gì đã tồn tại (xem Đo lường thành công: Analytics & Testing)

Vị trí của từng công cụ trong pipeline

Sẽ dễ hình dung hơn nếu coi toolchain là một pipeline với một nguồn chân lý duy nhất chảy theo hai hướng — vào design tool và vào codebase:

Token source to design and code
  1. Token sourceJSON / Tokens Studio / DTCG
  2. Figma (design)via Tokens Studio plugin, syncs to Figma Variables
    Style Dictionarybuild pipeline
  3. Figma UI Kitmirrors coded component API
    CSS custom properties
    iOS (Swift/.plist)
    Android (XML/Kotlin)
  4. designer composes screens
    Product design files
  5. Coded component libraryReact / Vue / Web Components
  6. developed & documented in
    Storybook

Hai nửa — design và code — không bao giờ nói chuyện trực tiếp với nhau. Cả hai cùng tiêu thụ (consume) một nguồn token, và lý tưởng là cùng một đặc tả (specification) component, giữ cho mọi thứ trung thực mà không cần một API sống nối giữa Figma và repo của bạn.

Khái niệm chính

Figma — design editor thống trị

Figma đã trở thành design tool mặc định cho các team xây dựng design system, vì những lý do đặc biệt quan trọng với công việc làm hệ thống chứ không chỉ thiết kế UI thông thường:

Không có gì trong số này là độc quyền mãi mãi của Figma, nhưng ở thời điểm hiện tại, đây là công cụ mà hầu hết tài liệu tham khảo về design system (kể cả roadmap.sh) đều mặc định, và hệ sinh thái plugin bên dưới là dành riêng cho Figma.

Figma plugin cho quy trình token

Figma Variables native đã bao phủ nhiều thứ mà trước đây cần plugin, nhưng Tokens Studio for Figma (trước đây là Figma Tokens) vẫn được dùng rộng rãi, đặc biệt khi team cần:

Pattern thực tế mà hầu hết team áp dụng: giá trị token nằm trong một (hoặc vài) file JSON trong cùng repository với thư viện component, Tokens Studio đồng bộ JSON đó vào Figma để designer làm việc với variable sống, và Style Dictionary (bên dưới) tiêu thụ cùng JSON đó để sinh ra artifact code. Một nguồn, hai bên tiêu thụ.

Định dạng file: JSON như định dạng interchange

JSON là định dạng interchange trên thực tế (de facto) cho design token vì nó có thể diff được trong git, không bị vendor lock-in, và mọi tool downstream (Figma plugin, Style Dictionary, script tùy chỉnh) đều parse được mà không cần SDK độc quyền. W3C Design Tokens Community Group (DTCG) format đang nổi lên như một chuẩn hình dạng cho JSON này để các tool ngừng tự bịa ra các dialect không tương thích:

{
  "color": {
    "brand": {
      "primary": { "$value": "#3B82F6", "$type": "color" },
      "primary-hover": { "$value": "#2563EB", "$type": "color" }
    }
  },
  "space": {
    "4": { "$value": "16px", "$type": "dimension" }
  }
}

Tổ chức file token thường chia theo tier (xem Design Token & Nền tảng thị giác cho mô hình tier core/semantic/component):

tokens/
  core.json         # raw scales: color.blue.500, space.4, font.size.16
  semantic.json      # aliases: color.brand.primary -> color.blue.500
  components.json    # component-scoped: button.padding-x -> space.4

Tổ chức file design: file library vs. file product

Một design system trong Figma hầu như luôn được chia thành ít nhất hai loại file:

Sự phân chia này quan trọng về mặt vận hành: nếu mọi product designer tự vẽ lại button ở local thay vì kéo instance từ library, thì khoảnh khắc library được cập nhật (ví dụ corner radius đổi trên toàn hệ thống), những button vẽ tay đó sẽ không bao giờ cập nhật theo — chính xác là vấn đề drift mà toàn bộ bài này tồn tại để ngăn chặn. Figma thể hiện điều này qua thông báo tường minh “N components have updates” khi source của một library instance thay đổi, đây là một trong số ít nơi design tooling đưa ra tín hiệu chủ động, tự động rằng có gì đó đã trôi dạt — gần giống điều mà package manager làm cho dependency của code hơn bất cứ thứ gì design tooling từng có một thập kỷ trước.

UI kit trong Figma như bản phản chiếu của thư viện đã code

UI kit là artifact phía Figma nên được đánh giá bằng một câu hỏi duy nhất: compose một màn hình bằng nó có tạo ra thứ mà engineer có thể build bằng thư viện đã code mà không cần dịch lại không? Điều đó đòi hỏi UI kit phải phản chiếu chính xác các component đã code:

API component đã codeComponent tương ứng trong Figma
<Button variant="primary" size="md" disabled />Component set Button với variant property variant, size, state
<Alert status="warning">Component set Alert với variant property status
Slot/children prop cho iconMột slot instance component lồng bên trong component Figma, có thể swap được
Responsive breakpoint trong CSSVariant frame/component tương ứng theo từng breakpoint, hoặc auto-layout resize tương đương

Khi UI kit chậm chân hơn thư viện đã code — một size="xs" mới ship trong code nhưng chưa được thêm thành variant Figma — designer hoặc không thể design state đó, hoặc tệ hơn, tự vẽ tay, mở lại vấn đề drift. Giữ cho hai bên đồng bộ là vấn đề governance và quy trình cũng nhiều như là vấn đề tooling (xem Governance & Đóng góp), nhưng yêu cầu về tooling là danh sách variant của UI kit nên được coi như được sinh ra/dẫn xuất từ cùng một contract component với code, chứ không được thiết kế độc lập.

Thư viện component đã code và Storybook

Thư viện đã code là phần triển khai thực sự — một package React, Vue, hoặc Web Components mà các codebase product cài đặt và import. Storybook đã trở thành công cụ chuẩn để phát triển và document các component này ở trạng thái cô lập, vì một lý do cụ thể: nó cho phép bạn render một component ở mọi trạng thái và mọi tổ hợp props mà không cần một ứng dụng chạy đầy đủ, một backend, hay dữ liệu thực. Sự cô lập đó là điều khiến nó hữu ích cả như một công cụ phát triển (build component trong một sandbox nhanh, tập trung) lẫn công cụ documentation (instance Storybook đã deploy chính là catalog component sống mà designer, PM, và engineer khác duyệt qua).

Một story Button tối giản, dùng Component Story Format 3 (CSF3) với @storybook/react:

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'destructive'],
    },
    size: {
      control: 'select',
      options: ['sm', 'md', 'lg'],
    },
    disabled: { control: 'boolean' },
  },
  args: {
    children: 'Button',
    variant: 'primary',
    size: 'md',
    disabled: false,
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: { variant: 'primary' },
};

export const Secondary: Story = {
  args: { variant: 'secondary' },
};

export const Destructive: Story = {
  args: { variant: 'destructive' },
};

export const Disabled: Story = {
  args: { disabled: true },
};

// One story rendering every variant/size combination at once,
// useful for visual regression snapshots and design review.
export const AllVariants: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
      {(['primary', 'secondary', 'destructive'] as const).map((variant) =>
        (['sm', 'md', 'lg'] as const).map((size) => (
          <Button key={`${variant}-${size}`} variant={variant} size={size}>
            {variant} / {size}
          </Button>
        )),
      )}
    </div>
  ),
};

Vài điểm ví dụ này minh họa mà đặc biệt quan trọng với quy trình design-to-dev:

Token pipeline tooling: Style Dictionary

Khi token đã tồn tại dưới dạng JSON, phải có gì đó biến nguồn duy nhất đó thành định dạng gốc của mọi nền tảng: CSS custom properties cho web, .xml/hằng số Kotlin cho Android, .swift/.plist cho iOS, và đôi khi xuất ngược lại thành JSON tiêu thụ được bởi Figma. Style Dictionary (ban đầu được xây dựng tại Amazon, giờ được dùng rộng rãi) là công cụ phổ biến nhất cho việc này: bạn định nghĩa token một lần, cấu hình một hoặc nhiều “platform,” và nó chạy các transform để sinh ra từng định dạng output.

Một config style-dictionary cơ bản và nguồn token:

// tokens/color.json
{
  "color": {
    "brand": {
      "primary": { "value": "#3B82F6" },
      "primary-hover": { "value": "#2563EB" }
    },
    "text": {
      "default": { "value": "#111827" },
      "muted": { "value": "#6B7280" }
    }
  },
  "space": {
    "1": { "value": "4px" },
    "2": { "value": "8px" },
    "4": { "value": "16px" },
    "8": { "value": "32px" }
  }
}
// style-dictionary.config.js
const StyleDictionary = require('style-dictionary');

module.exports = {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'build/css/',
      files: [
        {
          destination: 'variables.css',
          format: 'css/variables',
          options: { outputReferences: true },
        },
      ],
    },
    ios: {
      transformGroup: 'ios-swift',
      buildPath: 'build/ios/',
      files: [
        {
          destination: 'StyleDictionary.swift',
          format: 'ios-swift/class.swift',
          className: 'StyleDictionary',
        },
      ],
    },
    android: {
      transformGroup: 'android',
      buildPath: 'build/android/',
      files: [
        {
          destination: 'colors.xml',
          format: 'android/colors',
        },
        {
          destination: 'dimens.xml',
          format: 'android/resources',
        },
      ],
    },
    json: {
      // A flat JSON re-export, e.g. for a Figma plugin (Tokens Studio)
      // to pull built values back into the design tool.
      transformGroup: 'js',
      buildPath: 'build/json/',
      files: [
        {
          destination: 'tokens.json',
          format: 'json/flat',
        },
      ],
    },
  },
};

Chạy style-dictionary build --config style-dictionary.config.js sẽ sinh ra, trong số các file khác, một build/css/variables.css đại loại như:

:root {
  --color-brand-primary: #3B82F6;
  --color-brand-primary-hover: #2563EB;
  --color-text-default: #111827;
  --color-text-muted: #6B7280;
  --space-1: 4px;
  --space-2: 8px;
  --space-4: 16px;
  --space-8: 32px;
}

…và các khai báo native tương đương cho iOS và Android trong cùng một bước build, từ cùng một file nguồn. Đây chính là cơ chế cho phép designer đổi một giá trị JSON và, sau khi CI build sinh lại mọi output nền tảng, thay đổi đó chạm tới web, iOS, và Android cùng lúc thay vì ba engineer phải tự tay cập nhật ba file constants riêng biệt. Xem Design Token & Nền tảng thị giác để biết cách tổ chức các tier token mà pipeline này tiêu thụ.

Analytics cho component và tooling (ngắn gọn)

Một khi component và token đã tồn tại, một câu hỏi khác nảy sinh: chúng có thực sự được dùng không, và ai dùng? Instrument analytics về import/usage (component nào render nhiều nhất trong production, giá trị token nào thực sự được tham chiếu so với giá trị “chết” trong file design, component nào có bản tái triển khai không chính thức gần-trùng lặp) biến “chúng tôi nghĩ component này đang được dùng” thành một sự thật đo lường được, và là điều thiết yếu để ưu tiên bảo trì hoặc deprecation. Chủ đề này được bàn sâu hơn trong Đo lường thành công: Analytics & Testing; phiên bản ngắn gọn ở đây là: các lựa chọn tooling ở giai đoạn trước trong pipeline (một registry package code duy nhất, một instance Storybook duy nhất, tên token có cấu trúc) chính là thứ khiến việc đo lường đó khả thi về sau.

Best Practices

Chọn tech stack cho thư viện đã code

Quyết định tooling lớn nhất, khó đảo ngược nhất là thư viện component đã code được xây dựng bằng gì, và nó nên được dẫn dắt bởi số lượng frontend stack mà tổ chức thực sự đang chạy, chứ không phải bởi framework mà team design-system ưa thích.

Cách tiếp cậnMô tảPhù hợp nhấtĐánh đổi
Framework-specific (ví dụ: chỉ React, hoặc package React + package Vue riêng biệt)Component được build trực tiếp theo model component của một framework, dùng pattern native của nó (hooks, composition API, slot)Tổ chức đã chuẩn hóa trên một frontend framework duy nhấtBuild nhanh nhất, DX idiomatic theo framework tốt nhất, nhưng một stack thứ hai (giả sử một micro-frontend Vue) hoặc phải có package song song riêng, hoặc không được hệ thống hỗ trợ chút nào
Framework-agnostic (Web Components)Component được build bằng API Custom Elements / Shadow DOM native, sau đó các package wrapper mỏng phơi ra API idiomatic theo từng framework (wrapper React, wrapper Vue) trên cùng một element nền tảngTổ chức lớn với nhiều frontend stack, hoặc tầm nhìn dài hạn khi framework lựa chọn có thể thay đổiWeb Components lịch sử có những góc cạnh thô ráp hơn về form participation, SSR, và typing TypeScript so với một component React native, và bản thân lớp wrapper là công sức phụ để build và bảo trì
Nhiều nguồn framework, một thiết kế duy nhấtDuy trì các bản triển khai React và Vue (v.v.) thực sự riêng biệt, nhưng sinh ra cả hai từ cùng một nguồn token/spec và giữ chúng theo cùng một contract visual qua các test visual-regression chungTổ chức cần ergonomics native theo từng framework ở mỗi stack và đủ khả năng chịu chi phí bảo trì trùng lặpChi phí cao nhất; chỉ hợp lý khi wrapper “framework-agnostic” tỏ ra không đủ idiomatic với một team cụ thể

Một quy tắc kinh nghiệm: nếu hiện tại, và trong tương lai gần có thể dự đoán được, chỉ có đúng một frontend framework trên toàn bộ danh mục sản phẩm, thì build framework-specific gần như luôn là lựa chọn thực dụng — Web Components giải quyết một vấn đề bạn chưa gặp phải, với chi phí duy trì thực sự liên tục. Ngay khi một framework thứ hai xuất hiện (một vụ acquisition mang theo codebase Angular, một kiến trúc micro-frontend, một hybrid mobile-web), framework-agnostic bắt đầu đáng giá, nhưng ngay cả khi đó, nhiều team chọn pattern “wrapper” (Lit hoặc Web Components thuần bên dưới, các package wrapper React/Vue/Angular mỏng bên trên) thay vì bắt mọi team tiêu thụ phải viết markup Custom Element thô.

Giữ nguồn token là nơi duy nhất giá trị được chỉnh sửa thủ công

Mọi artifact downstream — Figma Variables, CSS custom properties, hằng số iOS/Android — nên được sinh ra, không bao giờ chỉnh sửa thủ công. Nếu một engineer nhận thấy một giá trị hex sai và “sửa luôn” trong variables.css mà không đụng vào JSON nguồn, thì lần build Style Dictionary tiếp theo sẽ âm thầm ghi đè (revert) bản sửa của họ và drift lại quay về từ hướng khác. Coi các thư mục output đã sinh ra như build artifact (thường được literally gitignore, sinh ra trong CI) chứ không phải file mà ai đó chỉnh sửa trực tiếp.

Version hóa token pipeline và thư viện component cùng nhau, nhưng không cần ràng buộc cadence release của chúng một cách không cần thiết

Một thay đổi token gây breaking (đổi tên color.brand.primary thành color.brand.default, ví dụ) nên tuân theo cùng kỷ luật semantic-versioning như một thay đổi breaking API component — xem Versioning & Quản lý Release. Vì token được tiêu thụ bởi cả UI kit lẫn thư viện đã code, một bump version major của token thường buộc phải có một release phối hợp trên cả hai phía, đó là lý do tốt để giữ package token nhỏ, ổn định, và thay đổi ít thường xuyên hơn từng component riêng lẻ.

Biến Storybook, chứ không phải file Figma, thành artifact được review cho việc triển khai

Review thiết kế diễn ra trong Figma; review triển khai nên diễn ra dựa trên một instance Storybook đã deploy (hoặc một bản preview deployment riêng cho từng PR), chứ không chỉ bằng cách đọc lại diff code. Điều này cho designer một cách để sign-off độ chính xác về visual mà không cần đọc TypeScript, và cho reviewer QA/accessibility một bề mặt ổn định, cô lập để test — xem Accessibility & Thiết kế bao trùm để biết cần kiểm tra gì trong review đó.

Tự động hóa tín hiệu “library has updates” ở cả hai phía

Ở phía design, dựa vào thông báo cập nhật library native của Figma thay vì audit thủ công xem ai đang dùng component đã lỗi thời. Ở phía code, coi mỗi release mới của thư viện component giống như bất kỳ bump dependency nào khác — PR tự động (kiểu Renovate/Dependabot), changelog, và diff visual-regression trước khi merge, chứ không phải một tin nhắn Slack thủ công kiểu “này, chúng tôi vừa ship v2, cập nhật khi nào rảnh nhé” mà chẳng ai hành động theo.

Tài liệu tham khảo

Part of the Design System Roadmap knowledge base.

Overview

Every hand-off between a designer and an engineer is an opportunity for drift. Without a shared vocabulary, a designer draws a button with 12px of padding and a #3B82F6 fill; the engineer builds it with 10px and #3B72F5 because that’s what the last ticket used. Multiply that by hundreds of screens and dozens of engineers, and a product slowly disintegrates into thousands of near-identical, slightly-wrong components. This isn’t a discipline problem — it’s a tooling problem. Nobody can hold “the exact right spacing scale” in their head across a hundred files.

A design system fixes this by making the system itself the source of truth, and tooling is what keeps design and code pointing at that same source instead of drifting into two parallel realities. This note covers the concrete toolchain that makes that possible: the design editor (almost always Figma today) and the plugins that turn it from a drawing tool into a token-aware system; the interchange formats (chiefly JSON) that let tokens flow between design and code; the UI kit that mirrors the coded library inside Figma; Storybook as the workbench where components are actually built and documented in code; Style Dictionary as the pipeline that turns one token source into every platform’s native format; and the tech-stack decision (framework-specific vs. framework-agnostic) that determines how far a single coded library can reach across an organization.

None of these tools are the design system — they’re the plumbing that makes a design system operationally real instead of a static PDF of guidelines nobody follows.

Fundamentals

The hand-off problem, precisely

“Design-to-dev hand-off” traditionally means: a designer finishes a Figma file, an engineer opens it, and the engineer manually reads off values — colors, spacing, font sizes, corner radii — and re-encodes them by hand into CSS or a StyleSheet object. Every one of those reads is a chance for a rounding error, a mis-click, or a “close enough” approximation. Worse, the two artifacts (the Figma file and the codebase) have no structural connection: if the designer nudges a spacing token next sprint, nothing tells the engineer their component is now stale.

A mature design system tooling chain removes as many of these manual re-encodings as possible:

Manual hand-off stepTooling replacement
Designer picks a color visually, engineer eyeballs the hex from Figma’s inspectorBoth reference the same named token (color.brand.primary) exported from one source
Engineer guesses spacing from a rulerSpacing tokens (space.4 = 16px) shared via Style Dictionary output
Designer redraws a button from scratch for a new screenDesigner drags a Button component instance from a shared Figma library with the same variants as the coded component
Engineer builds a component, hopes it matches design intentEngineer builds against Storybook, designer reviews the deployed Storybook instance instead of a static mock
”Is this component already built somewhere?” asked in SlackComponent/token usage analytics show what already exists (see Measuring Success: Analytics & Testing)

Where each tool sits in the pipeline

It helps to think of the toolchain as a pipeline with a single source of truth flowing in two directions — into the design tool and into the codebase:

Token source to design and code
  1. Token sourceJSON / Tokens Studio / DTCG
  2. Figma (design)via Tokens Studio plugin, syncs to Figma Variables
    Style Dictionarybuild pipeline
  3. Figma UI Kitmirrors coded component API
    CSS custom properties
    iOS (Swift/.plist)
    Android (XML/Kotlin)
  4. designer composes screens
    Product design files
  5. Coded component libraryReact / Vue / Web Components
  6. developed & documented in
    Storybook

The two halves — design and code — never talk to each other directly. They both consume the same token source and (ideally) the same component specification, which keeps them honest without requiring a live API between Figma and your repo.

Key Concepts

Figma as the dominant design editor

Figma has become the default design tool for teams building design systems, for reasons that matter specifically to systems work rather than general UI design:

None of this is unique to Figma forever, but as of today it’s the tool almost every design-system reference (including roadmap.sh) assumes, and the plugin ecosystem below is Figma-specific.

Figma plugins for token workflows

Figma’s native Variables cover a lot of what used to require a plugin, but Tokens Studio for Figma (formerly Figma Tokens) remains widely used, especially where teams need:

The practical pattern most teams land on: token values live in a JSON file (or a small set of them) in the same repository as the component library, Tokens Studio syncs that JSON into Figma so designers work with live variables, and Style Dictionary (below) consumes the same JSON to generate code artifacts. One source, two consumers.

File formats: JSON as the interchange format

JSON is the de facto interchange format for design tokens because it is diffable in git, has no vendor lock-in, and every downstream tool (Figma plugins, Style Dictionary, custom scripts) can parse it without a proprietary SDK. The W3C Design Tokens Community Group (DTCG) format is emerging as a standard shape for this JSON so tools stop inventing incompatible dialects:

{
  "color": {
    "brand": {
      "primary": { "$value": "#3B82F6", "$type": "color" },
      "primary-hover": { "$value": "#2563EB", "$type": "color" }
    }
  },
  "space": {
    "4": { "$value": "16px", "$type": "dimension" }
  }
}

Token file organization typically splits by tier (see Design Tokens & Visual Foundations for the core/semantic/component tier model):

tokens/
  core.json         # raw scales: color.blue.500, space.4, font.size.16
  semantic.json      # aliases: color.brand.primary -> color.blue.500
  components.json    # component-scoped: button.padding-x -> space.4

Design file organization: library file vs. product files

A design system in Figma is almost always split into at least two kinds of files:

This split matters operationally: if every product designer redraws buttons locally instead of pulling instances from the library, the moment the library updates (say, the corner radius changes system-wide), those hand-drawn buttons never update — exactly the drift problem this whole note exists to prevent. Figma surfaces this as an explicit “N components have updates” notification when a library instance’s source changes, which is one of the few places design tooling gives a proactive, automatic signal that something has drifted — closer to what a package manager does for code dependencies than anything design tooling had a decade ago.

The Figma UI kit as a mirror of the coded library

The UI kit is the Figma-side artifact that should be judged by one question: does composing a screen with it produce something an engineer can build with the coded library without translation? That requires the UI kit to mirror the coded components exactly:

Coded component APIFigma component equivalent
<Button variant="primary" size="md" disabled />Component set Button with variant properties variant, size, state
<Alert status="warning">Component set Alert with variant property status
Slot/children prop for iconA nested component instance slot inside the Figma component, swappable
Responsive breakpoints in CSSMatching frame/component variants sized per breakpoint, or auto-layout that resizes equivalently

When the UI kit lags behind the coded library — a new size="xs" ships in code but isn’t added as a Figma variant — designers either can’t design that state or, worse, draw it by hand, reopening the drift problem. Keeping the two in lockstep is a governance and process question as much as a tooling one (see Governance & Contribution), but the tooling requirement is that the UI kit’s variant list should be treated as generated/derived from the same component contract as the code, not designed independently.

The coded component library and Storybook

The coded library is the actual implementation — a React, Vue, or Web Components package that product codebases install and import. Storybook has become the standard tool for developing and documenting these components in isolation, for a specific reason: it lets you render one component in every state and prop combination without needing a full running application, a backend, or realistic data. That isolation is what makes it useful both as a development tool (build the component against a fast, focused sandbox) and a documentation tool (the deployed Storybook instance is the living component catalog designers, PMs, and other engineers browse).

A minimal Button story, using Component Story Format 3 (CSF3) with @storybook/react:

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'destructive'],
    },
    size: {
      control: 'select',
      options: ['sm', 'md', 'lg'],
    },
    disabled: { control: 'boolean' },
  },
  args: {
    children: 'Button',
    variant: 'primary',
    size: 'md',
    disabled: false,
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: { variant: 'primary' },
};

export const Secondary: Story = {
  args: { variant: 'secondary' },
};

export const Destructive: Story = {
  args: { variant: 'destructive' },
};

export const Disabled: Story = {
  args: { disabled: true },
};

// One story rendering every variant/size combination at once,
// useful for visual regression snapshots and design review.
export const AllVariants: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
      {(['primary', 'secondary', 'destructive'] as const).map((variant) =>
        (['sm', 'md', 'lg'] as const).map((size) => (
          <Button key={`${variant}-${size}`} variant={variant} size={size}>
            {variant} / {size}
          </Button>
        )),
      )}
    </div>
  ),
};

A few things this example demonstrates that matter for design-to-dev workflow specifically:

Token pipeline tooling: Style Dictionary

Once tokens exist as JSON, something has to turn that single source into every platform’s native format: CSS custom properties for web, .xml/Kotlin constants for Android, .swift/.plist for iOS, and sometimes a re-export back into Figma-consumable JSON. Style Dictionary (originally built at Amazon, now widely adopted) is the most common tool for this: you define your tokens once, configure one or more “platforms,” and it runs transforms to produce each output format.

A basic style-dictionary config and token source:

// tokens/color.json
{
  "color": {
    "brand": {
      "primary": { "value": "#3B82F6" },
      "primary-hover": { "value": "#2563EB" }
    },
    "text": {
      "default": { "value": "#111827" },
      "muted": { "value": "#6B7280" }
    }
  },
  "space": {
    "1": { "value": "4px" },
    "2": { "value": "8px" },
    "4": { "value": "16px" },
    "8": { "value": "32px" }
  }
}
// style-dictionary.config.js
const StyleDictionary = require('style-dictionary');

module.exports = {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'build/css/',
      files: [
        {
          destination: 'variables.css',
          format: 'css/variables',
          options: { outputReferences: true },
        },
      ],
    },
    ios: {
      transformGroup: 'ios-swift',
      buildPath: 'build/ios/',
      files: [
        {
          destination: 'StyleDictionary.swift',
          format: 'ios-swift/class.swift',
          className: 'StyleDictionary',
        },
      ],
    },
    android: {
      transformGroup: 'android',
      buildPath: 'build/android/',
      files: [
        {
          destination: 'colors.xml',
          format: 'android/colors',
        },
        {
          destination: 'dimens.xml',
          format: 'android/resources',
        },
      ],
    },
    json: {
      // A flat JSON re-export, e.g. for a Figma plugin (Tokens Studio)
      // to pull built values back into the design tool.
      transformGroup: 'js',
      buildPath: 'build/json/',
      files: [
        {
          destination: 'tokens.json',
          format: 'json/flat',
        },
      ],
    },
  },
};

Running style-dictionary build --config style-dictionary.config.js produces, among other files, a build/css/variables.css roughly like:

:root {
  --color-brand-primary: #3B82F6;
  --color-brand-primary-hover: #2563EB;
  --color-text-default: #111827;
  --color-text-muted: #6B7280;
  --space-1: 4px;
  --space-2: 8px;
  --space-4: 16px;
  --space-8: 32px;
}

…and equivalent native declarations for iOS and Android in the same build step, from the same source file. This is the mechanism that lets a designer change one JSON value and, after a CI build regenerates all platform outputs, have that change reach web, iOS, and Android simultaneously instead of three engineers manually updating three separate constants files. See Design Tokens & Visual Foundations for how to structure the token tiers this pipeline consumes.

Component and tooling analytics (brief)

Once components and tokens exist, a separate question emerges: are they actually used, and by whom? Instrumenting import/usage analytics (which components render most often in production, which token values are actually referenced versus dead in the design file, which components have near-duplicate unofficial reimplementations) turns “we think this component is used” into a measurable fact, and is essential for prioritizing maintenance or deprecation. This is covered in depth in Measuring Success: Analytics & Testing; the short version here is that tooling choices earlier in the pipeline (a single coded package registry, a single Storybook instance, structured token names) are exactly what make that instrumentation possible later.

Best Practices

Choosing a tech stack for the coded library

The single biggest, hardest-to-reverse tooling decision is what the coded component library is built in, and it should be driven by how many frontend stacks the organization actually runs, not by which framework the design-system team prefers.

ApproachDescriptionBest fitTradeoff
Framework-specific (e.g., React-only, or a React package + a separate Vue package)Components built directly against one framework’s component model, using its native patterns (hooks, composition API, slots)Organizations standardized on a single frontend frameworkFastest to build, best framework-idiomatic DX, but a second stack (say, a Vue micro-frontend) either gets its own parallel package or no system support at all
Framework-agnostic (Web Components)Components built with the native Custom Elements / Shadow DOM APIs, then thin wrapper packages expose framework-idiomatic APIs (React wrapper, Vue wrapper) over the same underlying elementLarger organizations with multiple frontend stacks, or long time horizons where the framework of choice may changeWeb Components historically have rougher edges around form participation, SSR, and TypeScript typing than a native React component, and the wrapper layer is itself extra work to build and maintain
Multi-framework source, single designMaintain genuinely separate React and Vue (etc.) implementations, but generate both from the same token/spec source and hold them to the same visual contract via shared visual-regression testsOrganizations that need framework-native ergonomics in each stack and can afford the duplicated maintenance costHighest cost; only justified when “framework-agnostic” wrappers prove insufficiently idiomatic for a given team

A rule of thumb: if there is currently, and will foreseeably be, exactly one frontend framework across the product portfolio, building framework-specific is almost always the pragmatic choice — Web Components solve a problem you don’t have yet, at a real ongoing cost. The moment a second framework enters the picture (an acquisition bringing an Angular codebase, a micro-frontend architecture, a mobile-web hybrid), framework-agnostic starts paying for itself, but even then, many teams choose the “wrapper” pattern (Lit or vanilla Web Components under the hood, thin React/Vue/Angular wrapper packages on top) rather than asking every consuming team to write raw Custom Element markup.

Keep the token source as the only place values are hand-edited

Every downstream artifact — Figma Variables, CSS custom properties, the iOS/Android constants — should be generated, never hand-edited. If an engineer notices a wrong hex value and “just fixes it” in variables.css without touching the JSON source, the next Style Dictionary build silently reverts their fix and the drift returns from a different direction. Treat generated output directories as build artifacts (often literally gitignored, generated in CI) rather than files anyone edits directly.

Version the token pipeline and the component library together, but don’t couple their release cadence unnecessarily

A breaking token change (renaming color.brand.primary to color.brand.default, for instance) should follow the same semantic-versioning discipline as a breaking component API change — see Versioning & Release Management. Because tokens are consumed by both the UI kit and the coded library, a major token version bump often forces a coordinated release across both, which is a good reason to keep the token package small, stable, and change it less often than individual components change.

Make Storybook, not the Figma file, the reviewable artifact for implementation

Design review happens in Figma; implementation review should happen against a deployed Storybook instance (or a PR-specific preview deployment of it), not by re-reading code diffs alone. This gives designers a way to sign off on visual accuracy without needing to read TypeScript, and gives QA/accessibility reviewers a stable, isolated surface to test against — see Accessibility & Inclusive Design for what to check for in that review.

Automate the “library has updates” signal on both sides

On the design side, rely on Figma’s native library-update notifications rather than manual audits of who’s using stale components. On the code side, treat a new component-library release the same way you’d treat any dependency bump — automated PRs (Renovate/Dependabot-style), changelogs, and visual-regression diffs before merging, not a manual “hey, we shipped v2, please update when you get a chance” Slack message that never gets actioned.

References