Clear, data-backed insights can go a long way in helping product teams adopt your design system. For years, we’ve struggled to establish a reliable and easily understood adoption metric for our design system at Mews—one that provides accurate data and resonates with all stakeholders.
In this article, I’ll walk you through the path we took to develop a metric that gives us accurate, granular data on design system coverage across our web products. I’ll share detailed explanations and code snippets so you can adapt the solution to your own needs. This isn’t meant to be a comprehensive guide to measuring design system adoption, but rather a look at our specific approach.
The challenges
We treat our design system as a product, and adoption metrics are the number one indicator of our success. There are multiple valid approaches to measuring adoption:
- Coverage: The best type of metric in my opinion. It provides a percentage of design system usage versus total usage, which is easily understood by everyone. Coverage can be measured in several ways—technically (via codebase imports), visually (by showing what percentage of the UI is composed of design system components), or based on renders (e.g. the percentage of total component renders that come from the design system). You can also measure token coverage as well as component coverage.
- Usage count: A useful metric showing how many design system elements are used in the product. While the absolute number doesn’t tell you if adoption is good or bad, tracking it over time shows adoption trends. Like coverage, you can track this in the codebase, product, or designs. You can measure usage of components, tokens, documentation, and other design system assets.
- Number of deviations: Not a direct adoption metric, more of an indicator of adoption. You can count custom components that aren’t from the design system or places where design system components were detached. The logic is simple—when products build or modify their own components instead of using the design system as is, it indicates lower adoption.
Our first design system metric was number of deviations, as it was the easiest way to measure adoption. Specifically, we measured the number of custom styles in products. It was a decent metric with two problems: non-technical stakeholders couldn’t easily understand its meaning, and it measured technical and design debt in products more than design system adoption.
The component identification problem
We really wanted to measure component adoption, but the surprising challenge was that measuring adoption is difficult when you don’t have a clear definition of what counts as a design system component. In our products, components are used in several ways:
- A standalone component: The design system provides a component and teams use it directly.
- Overridden or extended component: Teams import a component, override styles or add logic, then reuse it internally.
- Component within a composition: Teams combine multiple design system components and reuse the pattern internally.
Metric distortions
When considering how to measure adoption, taking the above factors into account, we realized all adoption metrics are distorted to some extent:
- Import-based measurement was inaccurate because components are often extended or adjusted and re-exported internally. This makes it very hard to track whether something should count as a design system component.
- Visual coverage measurement was too distorted because container components take most of the visual space, yet they’re just a fraction of the components on a page.
- Components have different complexity. A simple tag is much less complex than a datepicker, but in many metrics, they’re counted the same.
- Pages are dynamic now. Adoption would differ based on the user’s device, when dropdowns or dialogs appear, or what data is showing.
Off-the-shelf solutions
There are two libraries we considered when looking for a solution.
react-scanner
React Scanner tracks the number of usages of individual components and their props, which is useful for understanding component popularity and prioritization.
design-system-visual-coverage
Preply Design System Visual Coverage is currently the best open-source tool for measuring design system adoption. It calculates adoption by measuring visual coverage of design system components. Similar to the approach we eventually developed, it uses data attributes and works in production without performance impact.

Want to help Tomáš shape the future of our design system?
Check out our open roles and join the team!
React Scanner doesn’t provide the percentage-based coverage metrics we needed and suffers from the distortion issues mentioned earlier. The visual coverage tool gives too much importance to larger components. While it allows you to fix this by manually assigning weight to each component, we needed a solution that was simpler to maintain.
Our solution: Measuring at the HTML level

data-mds-element attributes added to design system elementsWe finally found a metric that ticks all the boxes: easy for stakeholders to understand, accurate without major distortions, and simple to measure and maintain.
Here’s our complete solution with source code.
Step one: Marking elements with data attributes
Instead of tracking components (which was problematic), we marked all DOM elements created by design system components using a babel plugin during build. This solves the weight problem naturally—complex components have more elements.
const markDesignSystemElements = () => {
// Configure these values based on your design system
const dataAttribute = 'data-ds-element';
const designSystemPackage = '@your-org/design-system';
const filePathPattern = /\/ui\/components\//;
const isDesignSystemComponent = (binding) => {
const filePath = binding.path.hub.file.opts.filename;
const importPath = binding.path.parent.source.value;
// Check if component is from design system package or internal components
return importPath.startsWith(designSystemPackage) ||
filePathPattern.test(filePath);
};
return {
visitor: {
JSXElement(path) {
const addAttribute = (element) => {
const { name } = element.openingElement.name;
const binding = path.scope.getBinding(name);
// Skip React fragments
if (name === 'Fragment') return;
// Add data attribute if this is a design system component
if (binding?.path.isImportSpecifier() && isDesignSystemComponent(binding)) {
const hasAttribute = element.openingElement.attributes.some(
attr => attr.name?.name === dataAttribute
);
if (!hasAttribute) {
element.openingElement.attributes.push({
type: 'JSXAttribute',
name: { type: 'JSXIdentifier', name: dataAttribute },
value: { type: 'StringLiteral', value: 'true' }
});
}
}
// Process child elements recursively
element.children.forEach(child => {
if (child.type === 'JSXElement') {
addAttribute(child);
}
});
};
addAttribute(path.node);
}
}
};
};
Step two: Calculating the adoption ratio
Once all design system elements are marked with data attributes, we can measure adoption with a simple script:
function calculateAdoption() {
const ignoredTags = ['html', 'head', 'body', 'script', 'style', 'link', 'meta', 'title', 'base', 'noscript','iframe', 'br', 'path', 'g', 'defs', 'rect', 'clippath', 'img'];
const totalElements = Array.from(document.querySelectorAll(`:not(${ignoredTags.join(',')})`));
const designSystemElements = document.querySelectorAll('[data-ds-element]');
return {
total: totalElements.length,
designSystem: designSystemElements.length,
adoption: ((designSystemElements.length / totalElements.length) * 100).toFixed(2)
};
}
calculateAdoption();
This script finds all page elements, removes elements that aren’t part of the interface, and calculates the ratio between design system elements and all elements. You can run it directly in your browser’s console or add it to your testing tools.
Step three: Automating measurement in production
To get consistent metrics with minimal distortion, we needed automated measurements in a stable environment. Since we already used New Relic, we implemented the adoption script to run every 10 seconds in production with results batched to avoid performance impacts. We put the measurement behind a feature flag, allowing us to control which environments should run the tracking. This gives us:
- Accurate results from real production environments with real data
- Capture of dynamic interactions like dialogs and dropdowns
- Large statistical sample (billions of events monthly) for consistent metrics
- Granular data showing adoption by route, team, and product
Measuring beyond adoption

Coverage metrics now form a core part of our measurement strategy. Most products are tracked with this implementation, giving us clear insights: our most complex product sits at 53% adoption with a steady 0.5% monthly increase, our guest-facing app reaches 60% (decent adoption given the customization needs of B2C experiences), and an acquired product shows rapid improvement at 5% monthly growth. The metric provides granular data—we can see adoption per product, per team, and per route. We share these insights with product teams so they can use the data in their own prioritization decisions.
We’re working to complement coverage metrics with usage data and user satisfaction measurements to get a complete picture of our design system’s health.
Wrapping up
We started with the challenge of measuring design system adoption accurately—existing metrics either weren’t understood by stakeholders or were too distorted to trust. By measuring at the HTML level using data attributes, we created a simple, accurate metric that everyone can understand. Now we have reliable data driving our design system decisions, helping teams adopt components more effectively, and proving the impact of our work.