Đo lường thành công: Analytics & TestingMeasuring Success: Analytics & Testing
Thuộc bộ kiến thức Design System Roadmap.
Tổng quan
Mọi design system đều phải cạnh tranh ngân sách với các tính năng tạo doanh thu trực tiếp — một luồng checkout mới, một gói giá mới, một tích hợp mới — và trong cuộc cạnh tranh đó, đội design system nào không trả lời được câu hỏi “chúng ta đã nhận được gì từ khoản đầu tư này?” bằng con số cụ thể sẽ là dòng ngân sách đầu tiên bị cắt khi công ty phải ưu tiên lại trong giai đoạn khó khăn. Đây không phải là bất công — đó là phản ứng hợp lý trước một sự bất định thật sự. Giá trị của design system phần lớn là gián tiếp — ít bug hơn, giao hàng nhanh hơn, giao diện nhất quán hơn — và giá trị gián tiếp không bao giờ được đo lường thì, dưới góc nhìn của người giữ ngân sách, không khác gì không có giá trị nào cả. Đội có thể nói “adoption rate tăng từ 40% lên 78% trên toàn bộ bề mặt sản phẩm sau bốn quý, và các đội dùng component của chúng tôi ship tính năng UI nhanh hơn 30%” đang đưa ra một lập luận sống sót được qua buổi review ngân sách; đội chỉ có thể nói “năm nay chúng tôi ship 40 component” đang mô tả sản lượng (output), không phải tác động (impact), và output đơn thuần không đủ để bảo vệ một dòng headcount.
Bài viết này bàn về kỷ luật đo lường giúp giá trị của design system trở nên rõ ràng, cụ thể: cần instrument (đo đạc) gì (usage của component, adoption, health, performance), cần test gì (tính đúng đắn khi render, accessibility, visual regression), và design system tương tác thế nào với văn hóa A/B test và experimentation rộng hơn của tổ chức sản phẩm. Không điều nào ở đây thay thế cho việc xây dựng quan hệ định tính đã nêu ở ./13-adoption-community-and-support.md — design system vẫn cần người ủng hộ và sự tin tưởng — nhưng thiện chí định tính đơn thuần không sống sót qua một bảng tính ngân sách. Đo lường là thứ biến “mọi người có vẻ thích design system” thành bằng chứng mà một cuộc trao đổi tài chính thực sự có thể sử dụng.
Vì sao “số lượng component đã xây” là chỉ số tiêu đề sai
Chỉ số vanity metric dễ bị cám dỗ nhất của một đội design system cũng chính là chỉ số kém hữu ích nhất: số lượng component đã ship. Nó dễ báo cáo, luôn có xu hướng tăng, và gần như không nói lên điều gì về tác động — một design system có 60 component nhưng chỉ 20% sản phẩm thực sự dùng đã tạo ra giá trị thực thấp hơn một design system chỉ có 25 component nhưng được dùng trên 80% sản phẩm. Component là tồn kho (inventory) của design system, không phải kết quả (outcome) của nó; một kho đầy hàng chưa bán không phải là một kết quả kinh doanh, dù kho có lớn đến đâu. Các chỉ số đáng báo cáo thay thế chính là những gì phần còn lại của bài viết này đề cập: adoption, health, và performance — mỗi chỉ số đo xem các component đang tồn tại có thực sự đang làm công việc thay thế UI tùy chỉnh, trùng lặp, thiếu nhất quán trên toàn sản phẩm hay không.
Kiến thức nền tảng
Component analytics: thực tế cái gì đang được dùng
Trước khi đội design system có thể khẳng định tác động, họ cần một câu trả lời thực tế, cập nhật cho một câu hỏi tưởng đơn giản nhưng khó trả lời: component nào thực sự được dùng, ở đâu, và tần suất bao nhiêu? Hai kỹ thuật bổ trợ nhau trả lời câu hỏi này, mỗi kỹ thuật có điểm mạnh và điểm mù riêng.
Static analysis (phân tích tĩnh) trên các import. Một script (hoặc công cụ có sẵn như react-scanner, một rule ESLint tùy chỉnh, hoặc đơn giản là grep/duyệt AST trên codebase) đếm xem bao nhiêu file import mỗi component từ package design system, và mỗi component được khởi tạo bao nhiêu lần. Cách này rẻ để xây dựng, không cần instrumentation runtime, và cho ra một bản kiểm kê đầy đủ, xác định — mọi repository tiêu thụ có thể được quét trong CI và kết quả được tổng hợp tập trung. Điểm mù của nó là nó đếm code có import component, không phải code path người dùng thực sự chạm tới — một Modal được import ở mười hai file có thể chỉ render cho 0.1% session nếu các file đó là các trang cài đặt hiếm khi được truy cập, và chỉ static analysis thôi không thể phân biệt điều đó với một Modal nằm trên luồng checkout chính.
Runtime telemetry (đo đạc thời gian chạy). Ghi log sự kiện nhẹ, có ý thức về quyền riêng tư ngay bên trong thư viện component (một sự kiện ẩn danh duy nhất khi mount, được gộp theo lô và gửi đến một endpoint analytics nội bộ) ghi nhận tần suất component thực sự render trong production, trên các session người dùng thật. Cách này khắc phục điểm mù ở trên — nó phân biệt được một Modal hiếm khi được truy cập với một Modal có traffic lớn — nhưng nó đòi hỏi thư viện component phải gửi kèm code instrumentation vào mọi sản phẩm tiêu thụ, dẫn đến các câu hỏi về kích thước bundle và quyền riêng tư được bàn ở phần sau bài viết.
Hai kỹ thuật này bổ trợ chứ không cạnh tranh nhau: static analysis trả lời “component này được tham chiếu ở đâu trong codebase”, điều mà một nỗ lực deprecation hay migration cần (phải tìm mọi call site để cập nhật); runtime telemetry trả lời “điều này thực sự quan trọng đến đâu với usage thật”, điều mà một quyết định ưu tiên cần (một component có hàng trăm call site nhưng gần như không render là một vấn đề bảo trì rất khác so với một component chỉ có mười call site nhưng render trên mọi session).
| Kỹ thuật | Trả lời câu hỏi gì | Điểm mù | Ứng dụng điển hình |
|---|---|---|---|
| Static import analysis | Component nào được tham chiếu, và ở đâu trong codebase | Không phản ánh traffic thực tế đến các code path đó | Lập kế hoạch deprecation, tìm mọi call site trước một breaking change |
| Runtime usage telemetry | Tần suất component thực sự render với người dùng thật | Cần instrumentation gửi đến mọi bên tiêu thụ; tốn công hơn và có bề mặt riêng tư lớn hơn | Ưu tiên đầu tư vào component có traffic cao; phát hiện component tưởng như được dùng nhưng thực chất “chết” trong production |
Biến component analytics thành tín hiệu deprecation
Lợi ích trực tiếp, thực tiễn nhất của component analytics là một danh sách xếp hạng các ứng viên gánh nặng bảo trì — những component tiêu tốn thời gian của đội design system (triage bug, duy trì tài liệu, review các thay đổi được đề xuất) mà không có mức usage thực tế tương xứng. Một component chỉ có năm call site, tất cả nằm trong một khu vực sản phẩm đã ship từ hai năm trước và đang bị khai tử, là ứng viên deprecation mạnh bất kể nó được xây dựng tốt đến đâu; một component có zero call site được static analysis phát hiện gần như chắc chắn là dead code nên được loại bỏ hoàn toàn khỏi package, giúp giảm cả kích thước bundle lẫn bề mặt mà đội phải bảo trì và tài liệu hóa. Làm rõ thứ hạng này — một bảng đơn giản gồm tên component, số call site, số lần render, và ngày sửa đổi gần nhất, được làm mới theo lịch — biến câu hỏi “có nên deprecate LegacyCard không?” từ một cuộc tranh luận cảm tính thành một đề xuất dựa trên dữ liệu, đồng thời cho việc ghi log cảnh báo deprecation được mô tả ở phần sau một danh sách ứng viên cụ thể để nhắm tới trước tiên.
Adoption metrics: thước đo thực sự cho sức khỏe của design system
Nếu component analytics trả lời “component cụ thể này có được dùng không”, adoption metrics trả lời câu hỏi tổng hợp mà ban lãnh đạo thực sự quan tâm: bao nhiêu phần trăm bề mặt UI của sản phẩm được xây từ design system, so với code legacy hoặc tùy chỉnh riêng lẻ? Đây là con số tiêu đề nên thay thế “số component đã ship” trong bất kỳ báo cáo trạng thái nào, vì nó đo trực tiếp thứ mà design system tồn tại để tạo ra — một lớp UI nhất quán, không trùng lặp — thay vì đo công sức bỏ ra để tạo ra nó.
Adoption thường được đo theo hai trục:
- Component adoption — tỷ lệ các phần tử UI (button, input, modal, card) trong bề mặt được render của sản phẩm đến từ thư viện dùng chung so với triển khai tùy chỉnh hoặc legacy. Có thể ước tính bằng kỹ thuật static analysis ở trên (import từ design system so với tổng số component-like JSX/markup trong codebase) hoặc đo chính xác hơn bằng một công cụ audit runtime kiểm tra DOM hoặc component tree trong production và phân loại từng phần tử được render.
- Token adoption — tỷ lệ các giá trị thị giác thô (mã hex màu, khoảng cách pixel hardcode, cỡ chữ tùy tiện) so với tham chiếu design token (
color.brand.primary,space.md) trong stylesheet và style props trên toàn codebase. Token adoption thường chậm hơn component adoption, vì một đội có thể dùng componentButtonnhưng vẫn tự viết tay một mã hex màu cho một banner dùng một lần — theo dõi riêng chỉ số này giúp lộ ra khoảng cách đó. Xem./03-design-tokens-and-visual-foundations.mdđể hiểu token là gì và vì sao tính nhất quán của token còn nền tảng hơn cả component adoption đối với sự mạch lạc thị giác.
Cả hai con số đều quan trọng nhất khi được nhìn dưới dạng xu hướng theo thời gian, không phải một ảnh chụp đơn lẻ — 55% adoption kể một câu chuyện rất khác nếu ba quý trước con số đó là 30% (tiến bộ mạnh mẽ, đáng tin cậy) so với nếu ba quý trước là 70% (một sự thụt lùi cần điều tra khẩn cấp, có thể do một đội mới với codebase mới gia nhập phép đo, một breaking change đẩy các đội quay lại fork riêng, hoặc chính băng thông của đội design system đã bị chuyển hướng khỏi việc hỗ trợ migration). Một dashboard adoption theo quý, phân theo khu vực sản phẩm hoặc theo đội, là một trong những công cụ thuyết phục nhất mà đội design system có thể mang vào cuộc trao đổi ngân sách, vì nó biến một tuyên bố trừu tượng (“chúng tôi đang làm mọi thứ nhất quán hơn”) thành một đường xu hướng cụ thể, có thể kiểm chứng.
| Trục adoption | Đo cái gì | Nguồn dữ liệu phổ biến |
|---|---|---|
| Component adoption | % phần tử UI được render từ thư viện dùng chung so với custom/legacy | Static import analysis, hoặc audit DOM/component-tree trong production |
| Token adoption | % giá trị thị giác (màu, spacing, typography) được biểu diễn dưới dạng tham chiếu token so với giá trị hardcode | Rule linting đếm giá trị hex/px thô so với lời gọi hàm token, chạy trong CI |
Service và health metrics
Bên cạnh usage và adoption, design system còn là một phần hạ tầng nội bộ với sức khỏe vận hành riêng, và bỏ quên nửa bức tranh này cuối cùng sẽ làm suy yếu chính adoption — không ai tiếp tục xây dựng trên một package có badge CI đỏ và backlog bug tồn đọng hai tháng. Ba health metric quan trọng nhất:
- Trạng thái build/CI của package design system — liệu package có build sạch, bộ test suite có pass, và pipeline release có xanh không, theo dõi dưới dạng phần trăm kiểu uptime theo thời gian. Một design system có CI không ổn định hoặc thường xuyên hỏng gửi tín hiệu bất ổn đến mọi đội đang cân nhắc phụ thuộc vào nó, bất kể bản thân component có tốt đến đâu.
- Thời gian giải quyết bug được báo cáo — thời gian trung vị và bách phân vị thứ 90 giữa lúc một bug được báo cáo trên một component dùng chung và lúc bản fix được ship. Đây là một trong những chỉ số nhạy cảm với niềm tin nhất trong toàn hệ thống: một chu kỳ fix bug chậm chính là trải nghiệm đẩy một đội đang thất vọng đi tới việc fork component và tự bảo trì bản sao riêng, đây là kết quả tồi tệ nhất cho adoption của design system, vì một fork chính là adoption đang âm thầm đảo ngược.
- Độ tươi mới của tài liệu (documentation freshness) — thời gian kể từ lần review hoặc cập nhật gần nhất của trang tài liệu mỗi component so với lần thay đổi code gần nhất của component đó; một trang tài liệu mô tả prop hay hành vi không còn tồn tại đang chủ động đánh lừa người tiêu thụ, và tệ hơn cả không có tài liệu, vì nó được tin tưởng theo đúng nghĩa đen. Theo dõi độ tươi mới (một ngày “last-reviewed” đơn giản, hoặc một kiểm tra CI gắn cờ tài liệu chưa được đụng tới cùng lúc với một breaking change) ngăn tài liệu mục nát một cách hệ thống, và kết nối trực tiếp với kỷ luật xem tài liệu như một hợp đồng đã nêu ở
./09-tooling-and-design-to-dev-workflow.md.
| Health metric | Theo dõi cái gì | Vì sao quan trọng |
|---|---|---|
| Trạng thái build/CI | % build/release pass, theo thời gian | Bất ổn ở đây làm suy yếu niềm tin vào mọi component mà package ship ra, bất kể chất lượng từng component |
| Thời gian giải quyết bug | Thời gian trung vị/p90 từ báo cáo bug đến khi fix được ship | Giải quyết chậm là nguyên nhân trực tiếp khiến các đội fork component thay vì phụ thuộc vào package dùng chung |
| Độ tươi mới tài liệu | Thời gian từ lần review gần nhất so với lần thay đổi code gần nhất | Tài liệu cũ chủ động đánh lừa người tiêu thụ, tệ hơn cả việc không có tài liệu |
Khái niệm chính
Logging: cái gì nên có trong một thư viện component dùng chung, và cái gì không nên
Một thư viện component dùng chung nằm trong render path của gần như mọi màn hình trong sản phẩm, điều này khiến nó trở thành nơi đặc biệt hấp dẫn — và cũng đặc biệt rủi ro — để thêm logging. Hai loại logging thực sự xứng đáng; hầu như mọi thứ khác có nguy cơ biến design system thành nguồn gây nhiễu, chi phí hiệu năng, hoặc rủi ro về quyền riêng tư.
Cảnh báo deprecation hiển thị cho developer đang tiêu thụ. Khi một prop, variant, hoặc toàn bộ component được đánh dấu deprecated, component đó nên phát ra một cảnh báo hướng tới developer (một console.warn chỉ trong development build, không bao giờ trong production) nêu tên API đã deprecated, lý do, phương án thay thế được khuyến nghị, và — lý tưởng nhất — một liên kết tới hướng dẫn migration. Đây là một trong những việc log có đòn bẩy cao nhất mà design system có thể làm, vì nó tiếp cận đúng đối tượng cần hành động (kỹ sư đang dùng API đã deprecated, ngay tại thời điểm họ đang thao tác trong đoạn code đó) mà không hề chạm tới người dùng cuối hay hệ thống telemetry production.
Usage telemetry, được giới hạn phạm vi chặt chẽ. Như đã nêu ở trên, một sự kiện “component này đã render” được ẩn danh, tổng hợp, phục vụ cho adoption và component-analytics metrics là có giá trị — nhưng nó phải được giới hạn phạm vi một cách có chủ đích: không có dữ liệu định danh người dùng, không thu thập giá trị của prop (điều dễ dàng rò rỉ nội dung người dùng — prop value của một TextField, nội dung title của một Modal — vào một pipeline analytics chưa bao giờ được thiết kế để xử lý điều đó), và lấy mẫu hoặc gộp theo lô thay vì phát một sự kiện cho mỗi lần render của mỗi component trên mỗi trang, điều có nguy cơ làm suy giảm rõ rệt hiệu năng thời gian chạy và nhấn chìm pipeline analytics trong khối lượng dữ liệu giá trị thấp.
Chế độ thất bại cần chủ động đề phòng là over-instrumentation (đo đạc quá mức): log mọi thay đổi prop, mọi lần re-render, mọi lần hover, “để phòng khi sau này hữu ích.” Điều này tạo ra ba chi phí cộng dồn — một bề mặt riêng tư không ai chính thức phê duyệt (giá trị prop chứa dữ liệu người dùng chảy vào một pipeline logging), một chi phí hiệu năng phải trả trên mỗi lần render của một số đoạn code được render thường xuyên nhất trong toàn sản phẩm, và một vấn đề nhiễu khi tín hiệu thực sự quan trọng (xu hướng adoption, khối lượng cảnh báo deprecation) bị chôn vùi dưới lượng telemetry không ai dùng đến. Bản năng đúng đắn là chỉ log mức tối thiểu cần để trả lời các câu hỏi cụ thể trong bài viết này — cái này có được dùng không, cái này đã deprecated nhưng vẫn còn dùng không, cái này có khỏe mạnh không — và coi mỗi dòng log mới được đề xuất là cần tự chứng minh giá trị của mình dựa trên một trong các câu hỏi đó, chứ không phải mặc định miễn phí.
| Loại logging | Ghi nhận cái gì | Nên xuất hiện ở đâu | Rủi ro nếu làm quá đà |
|---|---|---|---|
| Cảnh báo deprecation | Tên API deprecated, lý do, phương án thay thế, liên kết migration | Console của developer, chỉ trong development build | Nhiễu console lấn át cảnh báo thật nếu quá nhiều component bị deprecate cùng lúc |
| Usage telemetry | Sự kiện “component đã render” ẩn danh, tổng hợp | Pipeline analytics nội bộ, lấy mẫu/gộp theo lô | Chi phí hiệu năng và rủi ro riêng tư nếu thu thập giá trị prop hoặc phát không lấy mẫu trên mọi lần render |
Unit testing cho component: cái gì thực sự cần được cover
Một component dùng chung được thực thi bởi mọi đội import nó, nghĩa là một regression trong nó không phải là bug của riêng một đội — đó là bug của mọi đội tiêu thụ, cùng lúc, được phát hiện độc lập và báo cáo trùng lặp nhiều lần. Điều này nâng cao ngưỡng của khái niệm “được test đủ tốt” đối với một component design system, cao hơn hẳn so với một component tính năng dùng một lần thông thường cần, trải qua ba loại test riêng biệt trả lời các câu hỏi khác nhau.
Render với các tổ hợp prop khác nhau. Test cơ bản cho bất kỳ component nào: render nó với prop mặc định, với mọi variant đã tài liệu hóa, với mọi size đã tài liệu hóa, và với các giá trị prop biên (label rỗng, label cực dài, không có children, trạng thái disabled kết hợp với loading) — rồi khẳng định nó render mà không throw lỗi và cho ra output đúng như kỳ vọng. Điều này bắt được loại bug khi một tổ hợp prop trông có vẻ hợp lệ (chẳng hạn size="small" kết hợp với icon-only) chưa từng thực sự được thực thi và âm thầm bị hỏng.
Accessibility assertions (khẳng định về khả năng tiếp cận). Vì một component design system chính là hợp đồng accessibility cho mọi bên tiêu thụ dùng nó (xem ./07-accessibility-and-inclusive-design.md để có phần trình bày đầy đủ về accessibility), bộ test của nó nên khẳng định trực tiếp các thuộc tính accessibility, không chỉ tính đúng đắn về mặt thị giác. Cặp đôi tiêu chuẩn cho việc này trong hệ sinh thái React là Testing Library (khuyến khích truy vấn output đã render theo cách một người dùng hoặc công nghệ hỗ trợ (assistive technology) sẽ làm — theo role và accessible name, không phải theo CSS class hay test ID) kết hợp với jest-axe, chạy engine accessibility axe-core trên output đã render và làm fail test nếu phát hiện vi phạm (thiếu label, độ tương phản màu không đủ trong inline style, dùng ARIA attribute không hợp lệ, cấu trúc heading không đúng).
Một ví dụ tối giản cho pattern này, test một component Button cho cả việc gắn label đúng lẫn việc không có vi phạm a11y có thể tự động phát hiện được:
import { render, screen } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";
import { Button } from "./Button";
expect.extend(toHaveNoViolations);
test("Button exposes an accessible name and has no a11y violations", async () => {
const { container } = render(
<Button icon="trash" aria-label="Delete item" onClick={() => {}} />
);
// Truy vấn theo role + name, đúng như cách một screen reader sẽ làm
expect(
screen.getByRole("button", { name: "Delete item" })
).toBeInTheDocument();
// Chạy axe-core trên DOM đã render và fail nếu có vi phạm
const results = await axe(container);
expect(results).toHaveNoViolations();
});
test("disabled Button is exposed as disabled to assistive technology", () => {
render(<Button disabled>Submit</Button>);
expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
});
Lưu ý test này thực sự kiểm tra cái gì: không phải “button trông có đúng không”, mà là “người dùng bàn phím hoặc screen reader có thể tìm thấy nó, hiểu mục đích của nó, và nhận biết đúng trạng thái của nó không” — đây chính xác là hợp đồng mà một component dùng chung nợ mọi bên tiêu thụ, vì hầu hết các đội tiêu thụ sẽ không bao giờ tự kiểm tra lại accessibility và đang tin tưởng rằng design system đã làm đúng một lần, tập trung.
Visual regression testing. Test render và test accessibility bắt được lỗi về chức năng và ngữ nghĩa, nhưng không cái nào bắt được một regression thuần túy về thị giác — một giá trị padding âm thầm lệch 4px, một shadow biến mất, một token màu resolve sai giá trị sau một lần refactor token — nơi component vẫn render, vẫn pass mọi kiểm tra accessibility, nhưng vẫn sai về mặt pixel. Các công cụ visual regression (Chromatic, Percy, tính năng so sánh screenshot có sẵn của Playwright, hoặc test runner của Storybook kết hợp với dịch vụ diff screenshot) render mỗi story hoặc trạng thái đã tài liệu hóa, chụp screenshot, và diff từng pixel so với một baseline đã được duyệt, gắn cờ mọi khác biệt để con người review trước khi merge. Đây là lớp test bắt được chế độ thất bại mà unit test về cấu trúc không thể bắt được: tính đúng đắn của hình thức hiển thị, không chỉ tính đúng đắn của hành vi.
| Loại test | Bắt được gì | Không bắt được gì |
|---|---|---|
| Render với tổ hợp prop | Crash hoặc output sai với giá trị prop hợp lệ/biên | Hình thức thị giác, ngữ nghĩa accessibility |
| Accessibility assertions (Testing Library + jest-axe) | Thiếu accessible name, ARIA không hợp lệ, vấn đề contrast phát hiện được tĩnh | Regression thị giác ở mức pixel, một số kiểm tra a11y chỉ làm được thủ công (ví dụ thứ tự focus trên toàn trang) |
| Visual regression testing | Thay đổi hình thức ở mức pixel (spacing, màu, shadow lệch) | Bug về chức năng/hành vi, hầu hết vấn đề accessibility |
Best Practices
Performance: cho thư viện component performance budget của riêng nó
Một design system vốn nhằm giúp xây dựng sản phẩm nhanh hơn có thể dễ dàng trở thành nguyên nhân khiến sản phẩm tải chậm hơn, nếu hiệu năng của chính nó bị bỏ quên không quản lý — và vì mọi sản phẩm tiêu thụ đều import từ cùng một package, một regression ở đây bị nhân lên trên toàn bộ danh mục sản phẩm phụ thuộc vào nó, theo cách mà regression hiệu năng của riêng một sản phẩm không bao giờ có.
Kích thước bundle trên mỗi component. Hãy coi kích thước bundle như một budget, không phải một việc phụ: đặt giới hạn kích thước rõ ràng cho mỗi component (hoặc mỗi hạng mục — một Icon đơn giản nên có budget nhỏ hơn nhiều so với một DataTable), thực thi trong CI bằng công cụ như size-limit hoặc bundlesize, và làm fail build nếu một thay đổi đẩy component vượt quá budget mà không có ngoại lệ được xem xét rõ ràng. Nếu không quản lý, một thư viện component sẽ âm thầm tích lũy trọng lượng — một dependency được thêm ở đây, một code path không dùng được giữ lại ở kia — cho tới khi một sản phẩm chỉ import “mỗi một Button” lại kéo theo lượng JavaScript nhiều hơn hẳn giá trị của một button, do tree-shaking kém hoặc một chuỗi dependency vô tình bị bundle kèm theo.
Hiệu năng render của các component phức tạp. Kích thước bundle chi phối thời gian tải; hiệu năng render chi phối độ phản hồi khi chạy thời gian thực, và điều này quan trọng nhất với các component có khả năng xuất hiện trong các ngữ cảnh đòi hỏi cao — danh sách lớn, bảng dữ liệu, combobox với tập option lớn, form phức tạp có nhiều trường phụ thuộc lẫn nhau. Các component này nên được benchmark dưới khối lượng dữ liệu tình huống xấu nhất thực tế (một bảng có 10.000 dòng, không phải 10), sử dụng các kỹ thuật như virtualization (chỉ render các dòng đang nằm trong viewport), memoization các phép tính tốn kém, và profiling bằng chính công cụ hiệu năng của framework (chẳng hạn React DevTools Profiler) để phát hiện các component re-render nhiều hơn hẳn so với mức output hiển thị của chúng thay đổi.
| Khía cạnh hiệu năng | Cần budget/đo cái gì | Cơ chế thực thi |
|---|---|---|
| Kích thước bundle trên mỗi component | Số KB được thêm vào bundle của app tiêu thụ khi import component này | Kiểm tra size-limit trong CI, fail build khi có regression nếu không có ngoại lệ rõ ràng |
| Hiệu năng render | Thời gian render/update dưới khối lượng dữ liệu tình huống xấu nhất thực tế (danh sách lớn, bảng lớn) | Benchmark với khối lượng dữ liệu thực tế; profiling tần suất re-render; virtualization cho tập dữ liệu lớn |
A/B test và experiment: thiết kế component để hỗ trợ biến thể, không chống lại nó
Một tổ chức sản phẩm có văn hóa experimentation trưởng thành chạy A/B test liên tục trên các chi tiết UI — nội dung button, bố cục card, sự hiện diện hoặc kích thước của một hình ảnh, thứ tự các trường form — và một design system cứng nhắc quy định chỉ đúng một phiên bản “chính xác” cho mỗi component tự đặt mình vào thế xung đột trực tiếp, hoàn toàn có thể tránh được, với văn hóa đó. Nếu chạy một experiment đòi hỏi một đội phải fork một component (sao chép nó, chỉnh sửa bản fork, và tách khỏi nguồn dùng chung chỉ để test một biến thể), design system đã chủ động cản trở khả năng ra quyết định dựa trên dữ liệu của tổ chức — và mỗi fork như vậy, một lần nữa, chính là adoption đang âm thầm đảo ngược, đúng chế độ thất bại đã mô tả ở phần trên đối với việc fix bug chậm.
Pattern tốt hơn là để các component hỗ trợ điểm biến thể sạch (clean variation points) mà experiment có thể móc vào mà không cần fork: các prop variant và size của một component đã cung cấp sẵn một trục biến thể được chấp thuận; xa hơn nữa, các component dành cho các bề mặt nhiều experiment (trang marketing, luồng onboarding, trang giá) nên phơi bày prop dành riêng cho các chiều thường bị test: nội dung/copy dưới dạng prop thay vì children hardcode, bố cục hoặc thứ tự dưới dạng prop rõ ràng thay vì thứ tự markup cố định, mức độ nhấn mạnh thị giác (size, màu, độ nổi bật) dưới dạng variant thay vì style ghi đè một lần. Điều này cho phép một framework experimentation gán người dùng vào các biến thể và chỉ đơn giản truyền các giá trị prop khác nhau vào cùng một component dùng chung, giữ experiment nằm trong hợp đồng của design system thay vì bên ngoài nó.
Điều này không có nghĩa là design system nên chấp nhận bề mặt prop không giới hạn chỉ để hỗ trợ các experiment giả định trong tương lai — điều đó lại tái tạo vấn đề tràn lan size/state đã nêu ở ./06-component-guidelines-and-documentation-patterns.md. Phạm vi đúng đắn nên được xác định qua trao đổi với các đội thực sự chạy experiment: component nào nằm trên các bề mặt có tốc độ experiment cao, và chiều cụ thể nào (copy, layout, mức nhấn mạnh) bị thay đổi đủ thường xuyên để xứng đáng có một prop hạng nhất thay vì một bản fork một lần. Một design system tham gia vào experimentation như một nhu cầu sản phẩm hợp pháp, đang diễn ra — thay vì coi mọi biến thể được đề xuất là một sự lệch lạc khỏi “thiết kế đúng” — sẽ giành được sự tin tưởng của các đội growth và product, những đội vốn dĩ là những người có khả năng cao nhất tìm cách né tránh nó hoàn toàn.
Đưa việc đo lường vào cùng các diễn đàn xây dựng adoption
Chỉ riêng con số không đủ để tác động đến một cuộc trao đổi ngân sách nếu không ai nhìn thấy chúng cho tới khi cuộc trao đổi đã diễn ra. Hãy đưa xu hướng adoption, health metric, và mức tuân thủ performance budget vào cùng nhịp giao tiếp định kỳ đã mô tả ở ./13-adoption-community-and-support.md — office hours, bản tin, buổi review hàng quý — để khi một câu hỏi về ngân sách được đặt ra, câu trả lời đã là một hiện vật quen thuộc, đáng tin cậy, lặp lại đều đặn, chứ không phải một con số được tạo ra một cách phòng thủ dưới áp lực.
Bảng: hạng mục chỉ số → chỉ số ví dụ → vì sao quan trọng
| Hạng mục chỉ số | Chỉ số ví dụ | Vì sao quan trọng |
|---|---|---|
| Adoption | % bề mặt UI được render dùng component design system so với custom/legacy code, theo dõi hàng quý | Thước đo trực tiếp nhất cho việc liệu design system có thực sự đang thay thế UI không nhất quán, trùng lặp hay không — chính là kết quả nó tồn tại để tạo ra |
| Adoption | % giá trị thị giác được biểu diễn dưới dạng design token so với giá trị hardcode | Lộ ra khoảng cách mà component adoption đơn thuần che giấu — một đội có thể dùng component dùng chung trong khi vẫn tự viết tay màu và spacing |
| Health | Tỷ lệ build/CI pass của package design system theo thời gian | Bất ổn ở đây làm suy yếu niềm tin của mọi đội khi phụ thuộc vào package, bất kể chất lượng component |
| Health | Thời gian trung vị/p90 để giải quyết bug component được báo cáo | Fix chậm là nguyên nhân trực tiếp khiến các đội fork component thay vì tiếp tục dùng package dùng chung |
| Health | Độ tươi mới tài liệu (thời gian từ lần review gần nhất so với lần thay đổi code gần nhất) | Tài liệu cũ chủ động đánh lừa người tiêu thụ, tệ hơn cả việc không có tài liệu |
| Performance | Kích thước bundle thêm vào cho mỗi lần import component | Ngăn design system trở thành lý do khiến bundle của sản phẩm tiêu thụ phình to |
| Performance | Thời gian render/tần suất re-render của component phức tạp dưới khối lượng dữ liệu thực tế | Bắt được regression về độ phản hồi thời gian chạy trước khi nó lan tới mọi sản phẩm import component đó |
Tài liệu tham khảo
- Nielsen Norman Group — Design Systems 101 — cơ sở nghiên cứu cho việc đo lường tác động của design system
- Testing Library — Guiding Principles — truy vấn output đã render theo cách người dùng hoặc công nghệ hỗ trợ sẽ làm
- jest-axe — GitHub — khẳng định accessibility tự động ngay trong unit test
- Chromatic — Visual Testing Handbook — quy trình visual regression testing cho thư viện component
- web.dev — Performance budgets — thiết lập và thực thi performance budget
- size-limit — GitHub — thực thi budget kích thước bundle theo package/component trong CI
- react-scanner — GitHub — phân tích tĩnh usage component trên toàn codebase
- Optimizely — What is A/B testing? — nền tảng về thực hành experimentation mà design system cần hỗ trợ
Part of the Design System Roadmap knowledge base.
Overview
Every design system competes for budget against features that generate revenue directly — a new checkout flow, a new pricing tier, a new integration — and in that competition, a design system team that cannot answer “what did we get for this?” with numbers is the first line item cut when a downturn forces prioritization. This is not unfair; it is a reasonable response to genuine uncertainty. A design system’s value is mostly indirect — fewer bugs, faster delivery, more visual consistency — and indirect value that is never measured is indistinguishable, from a budget-holder’s perspective, from no value at all. The team that can say “adoption grew from 40% to 78% of product surface over four quarters, and teams using our components ship UI features 30% faster” is making a case that survives a budget review; the team that can only say “we shipped 40 components this year” is describing output, not impact, and output alone does not defend a headcount line.
This note is about building the measurement discipline that makes a design system’s value legible: what to instrument (component usage, adoption, health, performance), what to test (rendering correctness, accessibility, visual regressions), and how a design system interacts with the broader product organization’s A/B testing and experimentation culture. None of this replaces the qualitative relationship-building covered in ./13-adoption-community-and-support.md — a design system still needs advocates and trust — but qualitative goodwill alone does not survive a budget spreadsheet. Measurement is what turns “people seem to like the design system” into evidence a finance conversation can actually use.
Why “number of components built” is the wrong headline metric
A design system team’s most tempting vanity metric is also its least useful one: the count of components shipped. It is easy to report, always trends upward, and says essentially nothing about impact — a design system with 60 components that 20% of the product actually uses has delivered less real value than one with 25 components used across 80% of the product. Components are the design system’s inventory, not its outcome; a warehouse full of unsold stock is not a business result, no matter how large the warehouse is. The metrics worth reporting instead are the ones covered in the rest of this note: adoption, health, and performance — each of which measures whether the components that exist are actually doing the job of replacing inconsistent, duplicated, custom-built UI across the product.
Fundamentals
Component analytics: what is actually being used
Before a design system team can claim impact, it needs a factual, current answer to a deceptively hard question: which components are actually used, where, and how often? Two complementary techniques answer this, each with different strengths and blind spots.
Static analysis of imports. A script (or an existing tool like react-scanner, a custom ESLint rule, or a simple grep/AST-walk over the codebase) counts how many files import each component from the design system package, and how many times each is instantiated. This is cheap to build, requires no runtime instrumentation, and produces a complete, deterministic inventory — every consuming repository can be scanned in CI and the results aggregated centrally. Its blind spot is that it counts code that imports the component, not code paths users actually hit — a Modal imported in twelve files might render for 0.1% of sessions if those files are rarely-visited settings pages, and static analysis alone cannot distinguish that from a Modal on the primary checkout flow.
Runtime telemetry. Lightweight, privacy-conscious event logging inside the component library itself (a single anonymized event on mount, batched and sent to an internal analytics endpoint) captures how often a component actually renders in production, across real user sessions. This closes the blind spot above — it distinguishes a rarely-visited Modal from a heavily-trafficked one — but it requires the component library to ship instrumentation code into every consuming product, which raises the bundle-size and privacy questions covered later in this note.
The two techniques are complementary rather than competing: static analysis answers “where in the codebase is this component referenced,” which is what a deprecation or migration effort needs (it must find every call site to update); runtime telemetry answers “how much does this actually matter to real usage,” which is what a prioritization decision needs (a component with a hundred call sites but near-zero renders is a very different maintenance problem than a component with ten call sites that renders on every session).
| Technique | Answers | Blind spot | Typical use |
|---|---|---|---|
| Static import analysis | Which components are referenced, and where in the codebase | Doesn’t reflect real traffic to those code paths | Deprecation planning, finding every call site before a breaking change |
| Runtime usage telemetry | How often a component actually renders for real users | Requires instrumentation shipped to every consumer; more effort and more privacy surface | Prioritizing investment toward high-traffic components; catching a component that looks used but is dead in production |
Turning component analytics into a deprecation signal
The direct, practical payoff of component analytics is a ranked list of maintenance-burden candidates — components that consume design-system-team time (bug triage, documentation upkeep, review of proposed changes) without a matching amount of actual usage. A component with five call sites, all in a product area that shipped two years ago and is being sunset, is a strong deprecation candidate regardless of how well-built it is; a component with zero call sites detected by static analysis is very likely dead code that should be removed from the package outright, shrinking both the bundle and the surface area the team has to maintain and document. Making this ranking explicit — a simple table of component name, call-site count, render count, and last-modified date, refreshed on a schedule — turns “should we deprecate LegacyCard?” from a subjective argument into a data-backed proposal, and gives the deprecation-warning logging described later in this note a concrete list of candidates to target first.
Adoption metrics: the real measure of design system health
If component analytics answers “is this specific component used,” adoption metrics answer the aggregate question that actually matters to leadership: what percentage of the product’s UI surface is built from the design system, versus legacy or one-off custom code? This is the headline number that should replace “components shipped” in any status report, because it directly measures the thing a design system exists to produce — a consistent, non-duplicated UI layer — rather than the effort spent producing it.
Adoption is typically measured along two axes:
- Component adoption — the proportion of UI elements (buttons, inputs, modals, cards) in the product’s rendered surface that come from the shared library versus a custom or legacy implementation. This can be approximated by the static-analysis technique above (design-system imports vs. total component-like JSX/markup in a codebase) or measured more precisely with a runtime audit tool that inspects the DOM or component tree in production and classifies each rendered element.
- Token adoption — the proportion of raw visual values (hex colors, hardcoded pixel spacings, ad hoc font sizes) versus design-token references (
color.brand.primary,space.md) in the stylesheets and style props across the codebase. Token adoption often lags component adoption, because a team can adopt theButtoncomponent while still hand-writing a hex color for a one-off banner — tracking it separately surfaces that gap. See./03-design-tokens-and-visual-foundations.mdfor what a token is and why token consistency underpins visual coherence even more directly than component adoption does.
Both figures matter most as a trend over time, not a single snapshot — 55% adoption is a very different story if it was 30% three quarters ago (strong, credible progress) versus if it was 70% three quarters ago (a regression worth urgently investigating, possibly caused by a new team’s codebase joining the measurement, a breaking change that pushed teams back to forks, or the design system team’s own bandwidth having shifted away from migration support). A quarterly adoption dashboard, segmented by product area or team, is one of the single most persuasive artifacts a design system team can bring into a budget conversation, because it converts an abstract claim (“we’re making things more consistent”) into a concrete, falsifiable trend line.
| Adoption axis | What it measures | Common data source |
|---|---|---|
| Component adoption | % of rendered UI elements from the shared library vs. custom/legacy | Static import analysis, or a DOM/component-tree audit in production |
| Token adoption | % of visual values (color, spacing, type) expressed as token references vs. hardcoded literals | Linting rule counting raw hex/px values vs. token function calls, run in CI |
Service and health metrics
Alongside usage and adoption, a design system is also a piece of internal infrastructure with its own operational health, and neglecting that half of the picture eventually undermines adoption itself — nobody keeps building on a package with a red CI badge and a two-month bug backlog. Three health metrics matter most:
- Build/CI status of the design system package — whether the package builds cleanly, its test suite passes, and its release pipeline is green, tracked as a simple uptime-style percentage over time. A design system with flaky or frequently-broken CI signals instability to every team deciding whether to depend on it, independent of how good the components themselves are.
- Time-to-resolve reported bugs — the median and 90th-percentile time between a bug being reported against a shared component and a fix shipping. This is one of the most trust-sensitive metrics in the whole system: a slow bug-fix cycle is exactly the experience that pushes a frustrated team toward forking the component and maintaining their own copy, which is the single worst outcome for a design system’s adoption number, because a fork is adoption silently reversing.
- Documentation freshness — how long since each component’s documentation page was last reviewed or updated relative to the component’s last code change; a page documenting props or behavior that no longer exist actively misleads consumers and is worse than no documentation at all, because it’s trusted at face value. Tracking freshness (a simple “last-reviewed” date, or a CI check flagging documentation that hasn’t been touched alongside a breaking code change) prevents systemic doc rot, and connects directly to the documentation-as-contract discipline in
./09-tooling-and-design-to-dev-workflow.md.
| Health metric | What it tracks | Why it matters |
|---|---|---|
| Build/CI status | % of builds/releases passing, over time | Instability here undermines trust in every component the package ships, regardless of individual component quality |
| Time-to-resolve bugs | Median/p90 time from bug report to shipped fix | Slow resolution is the direct trigger for teams forking components instead of depending on the shared package |
| Documentation freshness | Time since last review vs. time since last code change | Stale docs actively mislead consumers, which is worse than no documentation |
Key Concepts
Logging: what belongs in a shared component library, and what doesn’t
A shared component library sits in the render path of potentially every screen in the product, which makes it a uniquely tempting — and uniquely risky — place to add logging. Two kinds of logging earn their keep; almost everything else risks turning the design system into a source of noise, performance overhead, or a privacy liability.
Deprecation warnings surfaced to consuming developers. When a prop, variant, or entire component is marked deprecated, the component should emit a developer-facing warning (a console.warn in development builds only, never in production) naming the deprecated API, the reason, the recommended replacement, and — ideally — a link to a migration guide. This is one of the highest-leverage things a design system can log, because it reaches the exact audience that needs to act (the engineer using the deprecated API, at the moment they’re actively working in that code) without touching end users or production telemetry at all.
Usage telemetry, scoped tightly. As covered above, an anonymized, aggregated “this component rendered” event feeding adoption and component-analytics metrics is valuable — but it must be scoped deliberately: no user-identifying data, no capturing of prop values (which can easily leak user content — a TextField’s value prop, a Modal’s title text — into an analytics pipeline never designed to handle it), and sampling or batching rather than firing an event on every single render of every single component on every single page, which risks measurably degrading runtime performance and drowning the analytics pipeline in low-value volume.
The failure mode to actively guard against is over-instrumentation: logging every prop change, every re-render, every hover, “just in case it’s useful later.” This produces three compounding costs — a privacy surface nobody explicitly signed off on (prop values containing user data flowing into a logging pipeline), a performance cost paid on every render of some of the most frequently-rendered code in the entire product, and a noise problem where the signal that actually matters (adoption trend, deprecation-warning volume) gets buried under telemetry nobody is using. The right instinct is to log the minimum needed to answer the specific questions in this note — is this used, is this deprecated-and-still-in-use, is this healthy — and treat every proposed new log line as needing to justify itself against one of those questions, not as free.
| Logging type | What it captures | Where it should appear | Risk if overdone |
|---|---|---|---|
| Deprecation warning | Deprecated API name, reason, replacement, migration link | Developer console, development builds only | Console noise drowning out real warnings if too many components deprecate at once |
| Usage telemetry | Anonymized “component rendered” event, aggregated | Internal analytics pipeline, sampled/batched | Performance overhead and privacy exposure if it captures prop values or fires unsampled on every render |
Unit testing for components: what actually needs coverage
A shared component is executed by every team that imports it, which means a regression in it is not a single team’s bug — it’s every consuming team’s bug, simultaneously, discovered independently and reported redundantly. This raises the bar for what “adequately tested” means for a design system component well above what a typical one-off feature component needs, across three distinct kinds of testing that answer different questions.
Rendering with different prop combinations. The baseline test for any component: render it with its default props, with every documented variant, with every documented size, and with edge-case prop values (an empty string label, an extremely long label, no children, a disabled state combined with a loading state) — and assert that it renders without throwing and produces the expected output. This catches the class of bug where a valid-looking combination of props (say, size="small" together with icon-only) was never actually exercised and silently breaks.
Accessibility assertions. Because a design system component is the accessibility contract for every consumer that uses it (see ./07-accessibility-and-inclusive-design.md for the full accessibility treatment), its test suite should assert accessibility properties directly, not just visual correctness. The standard pairing for this in the React ecosystem is Testing Library (which encourages querying the rendered output the way a user or assistive technology would — by role and accessible name, not by CSS class or test ID) combined with jest-axe, which runs the axe-core accessibility engine against the rendered output and fails the test if it detects violations (missing labels, insufficient color contrast in inline styles, invalid ARIA attribute usage, improper heading structure).
A minimal example of the pattern, testing a Button component for both correct labeling and the absence of automatically-detectable a11y violations:
import { render, screen } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";
import { Button } from "./Button";
expect.extend(toHaveNoViolations);
test("Button exposes an accessible name and has no a11y violations", async () => {
const { container } = render(
<Button icon="trash" aria-label="Delete item" onClick={() => {}} />
);
// Queries by accessible role + name, the way a screen reader would
expect(
screen.getByRole("button", { name: "Delete item" })
).toBeInTheDocument();
// Runs axe-core against the rendered DOM and fails on any violation
const results = await axe(container);
expect(results).toHaveNoViolations();
});
test("disabled Button is exposed as disabled to assistive technology", () => {
render(<Button disabled>Submit</Button>);
expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
});
Note what this test actually checks: not “does the button look right,” but “can a keyboard or screen-reader user find it, understand its purpose, and correctly perceive its state” — which is exactly the contract a shared component owes every consumer, since most consuming teams will never re-verify accessibility themselves and are trusting the design system to have gotten it right once, centrally.
Visual regression testing. Rendering and accessibility tests catch functional and semantic breakage, but neither catches a purely visual regression — a padding value that silently shifted by 4px, a shadow that disappeared, a color token that resolved to the wrong value after a token refactor — where the component still renders, still passes every accessibility check, and is still pixel-wrong. Visual regression tools (Chromatic, Percy, Playwright’s built-in screenshot comparison, or Storybook’s test runner combined with a screenshot diffing service) render each documented story or state, capture a screenshot, and diff it pixel-by-pixel against an approved baseline, flagging any difference for human review before merge. This is the testing layer that catches the failure mode unit tests structurally cannot: correctness of appearance, not just correctness of behavior.
| Test type | What it catches | What it does not catch |
|---|---|---|
| Rendering with prop combinations | Crashes or incorrect output for valid/edge-case prop values | Visual appearance, accessibility semantics |
| Accessibility assertions (Testing Library + jest-axe) | Missing accessible names, invalid ARIA, contrast issues detectable statically | Pixel-level visual regressions, some manual-only a11y checks (e.g. focus order across a full page) |
| Visual regression testing | Pixel-level appearance changes (spacing, color, shadow drift) | Functional/behavioral bugs, most accessibility issues |
Best Practices
Performance: give the component library its own performance budget
A design system that is meant to make products faster to build can easily become the reason a product is slower to load, if its own performance is left unmanaged — and because every consuming product imports from the same package, a regression here is multiplied across the entire portfolio of products depending on it, in a way a single product’s own performance regression never is.
Bundle size per component. Treat bundle size as a budget, not an afterthought: set an explicit size limit per component (or per category — a simple Icon should have a far smaller budget than a DataTable), enforce it in CI with a tool like size-limit or bundlesize, and fail the build if a change pushes a component over its budget without an explicit, reviewed exception. Left unmanaged, a component library accumulates weight silently — an added dependency here, an unused code path retained there — until a product that imports “just a Button” pulls in far more than a button’s worth of JavaScript because of poor tree-shaking or an accidentally-bundled dependency chain.
Render performance of complex components. Bundle size governs load time; render performance governs runtime responsiveness, and it matters most for the components most likely to appear in demanding contexts — large lists, data tables, comboboxes with large option sets, complex forms with many interdependent fields. These components should be benchmarked under realistic worst-case data volumes (a table with 10,000 rows, not 10), using techniques such as virtualization (rendering only the rows currently in the viewport), memoization of expensive computations, and profiling with the framework’s own performance tools (React DevTools Profiler, for instance) to catch components that re-render far more often than their visible output changes.
| Performance dimension | What to budget/measure | Enforcement mechanism |
|---|---|---|
| Bundle size per component | KB added to a consuming app’s bundle for importing this component | CI size-limit check, failing the build on regression without an explicit exception |
| Render performance | Time to render/update under realistic worst-case data volume (large lists, tables) | Benchmarking with realistic data volumes; profiling re-render frequency; virtualization for large datasets |
A/B tests and experiments: design components to support variation, not fight it
A product organization with a mature experimentation culture runs continuous A/B tests on UI details — button copy, card layout, the presence or size of an image, the order of form fields — and a design system that hardcodes exactly one “correct” version of every component puts itself in direct, avoidable conflict with that culture. If running an experiment requires a team to fork a component (copy it, modify the fork, and diverge from the shared source just to test a variant), the design system has actively obstructed the organization’s ability to make data-driven decisions — and every such fork is, again, adoption silently reversing, exactly the failure mode described earlier for slow bug fixes.
The better pattern is for components to support clean variation points that experiments can hook into without forking: a component’s variant and size props already provide one axis of sanctioned variation; beyond that, components intended for experimentation-heavy surfaces (marketing pages, onboarding flows, pricing pages) should expose props specifically for the dimensions that tend to get tested — copy/content as props rather than hardcoded children, layout or ordering as an explicit prop rather than fixed markup order, visual emphasis (size, color, prominence) as a variant rather than a one-off style override. This lets an experimentation framework assign users to variants and simply pass different prop values into the same shared component, keeping the experiment inside the design system’s contract rather than outside it.
This does not mean a design system should accept unlimited prop surface just to support hypothetical future experiments — that reintroduces the size/state sprawl problem covered in ./06-component-guidelines-and-documentation-patterns.md. The right scope is informed by conversation with the teams who actually run experiments: which components sit on high-experiment-velocity surfaces, and which specific dimensions (copy, layout, emphasis) get varied often enough to deserve a first-class prop rather than a one-off fork. A design system that engages with experimentation as a legitimate, ongoing product need — rather than treating every proposed variant as a deviation from “the correct design” — earns trust with the growth and product teams who are otherwise the most likely to route around it entirely.
Bring measurement into the same forums that build adoption
Numbers alone don’t move a budget conversation if nobody sees them until the conversation is already happening. Feed the adoption trend, health metrics, and performance budget compliance into the same regular communication cadence described in ./13-adoption-community-and-support.md — office hours, a newsletter, a quarterly review — so that by the time a budget question is asked, the answer is already a familiar, trusted, recurring artifact rather than a number produced defensively under pressure.
Table: metric category → example metric → why it matters
| Metric category | Example metric | Why it matters |
|---|---|---|
| Adoption | % of rendered UI surface using design-system components vs. custom/legacy code, tracked quarterly | The single most direct measure of whether the design system is actually replacing inconsistent, duplicated UI — the outcome it exists to produce |
| Adoption | % of visual values expressed as design tokens vs. hardcoded literals | Surfaces gaps that component adoption alone hides — a team can use shared components while still hand-writing colors and spacing |
| Health | Build/CI pass rate for the design system package over time | Instability here undermines every team’s confidence in depending on the package, regardless of component quality |
| Health | Median/p90 time-to-resolve reported component bugs | Slow fixes are the direct trigger for teams forking components instead of staying on the shared package |
| Health | Documentation freshness (time since last review vs. last code change) | Stale docs actively mislead consumers, which is worse than having no documentation |
| Performance | Bundle size added per component import | Prevents the design system itself from becoming the reason a consuming product’s bundle bloats |
| Performance | Render time/re-render frequency for complex components under realistic data volume | Catches runtime responsiveness regressions before they reach every product that imports the component |
References
- Nielsen Norman Group — Design Systems 101 — research-backed rationale for measuring design system impact
- Testing Library — Guiding Principles — querying rendered output the way a user or assistive technology would
- jest-axe — GitHub — automated accessibility assertions inside unit tests
- Chromatic — Visual Testing Handbook — visual regression testing workflow for component libraries
- web.dev — Performance budgets — setting and enforcing a performance budget
- size-limit — GitHub — CI enforcement of per-package/per-component bundle size budgets
- react-scanner — GitHub — static analysis of component usage across a codebase
- Optimizely — What is A/B testing? — background on experimentation practices a design system needs to support