Identify your Design System with class
In the early stages of developing the Spendesk design system, known as Grapes, we needed a easy and reliable method to identify a component.
At that time, the adoption of our design system was quite low, and our primary focus was on increasing usage by delivering features and components that would drive adoption. We didn’t have UI regression tests back then nor the test coverage we have today.
Although our design system was limited to some pages, every UI bug was already sent to the design system team.
Example of people asking on Slack support for a broken UI coming from component outside the design system
So in order to stop being overwhelmed by issue support, we made sure that everyone could quickly identify whetever an element originated from the design system or not.
CSS class
To help us classified any issue coming in our way, we decided to rename every CSS class in our Design System and prefix it with _grapes.
The highlighted element is from the Design System because its classes start with _grapes
This way, by simply inspecting the DOM, anyone could quickly see where an element came from. This approach saved us hundreds of hours in debugging.
Rename at build time
It’s worth noting that internally, we did not manually rename every single CSS class. Our Design System uses CSS Modules, which allowed us to leverage our build tool to handle the renaming for us.
With Vite, we simply needed to provide a function to the generateScopedName option.
export default defineConfig({
css: {
modules: {
generateScopedName: getReducedClassName,
},
},
});
getReducedClassName is a function that takes a name and a filename and returns a CSS class name in the format _grapes-XX, where XX are random letters and numbers drawn from the following alphabet:
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
Using only 2 characters provides about 3800 possibilities, which is enough for our use case.
Our function getReducedClassName looks like this:
export function getReducedClassName(name, filename) {
const key = `${name}${filename}`;
const savedClassName = map.get(key);
if (savedClassName) {
return savedClassName;
}
const className = generateClassName();
map.set(key, className);
return className;
}
With the name and filename arguments, we create a key. This key is used to fetch a previously generated class name. If none is found, a new one is generated and attached to this key for later use.
Finally, generateClassName is a simple function that returns a random CSS class based on the pattern _grapes-XX described earlier.
If you are interesed by the full code, here is everything:
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
const alphabetLength = alphabet.length;
const index = [0, 0];
const map = new Map();
/**
* Generates a minimal className in the format `_grapes-XX`.
* Handles 3844 possibilities, throws after
* @returns string
*/
function generateClassName() {
if (index[1] === alphabetLength) {
throw new Error('Not enough possibility');
}
const className = `_grapes-${alphabet[index[0]]}${alphabet[index[1]]}`;
if (++index[0] === alphabetLength) {
index[0] = 0;
++index[1];
}
return className;
}
/**
* Given a css name and a filename, returns a unique, minimal className
* @param {string} name
* @param {string} filename
* @returns string
*/
export function getReducedClassName(name, filename) {
const key = `${name}${filename}`;
const savedClassName = map.get(key);
if (savedClassName) {
return savedClassName;
}
const className = generateClassName();
map.set(key, className);
return className;
}
Legacy and benefits
Our design system is now backed by UI regression tests, and over 98% of our codebase is covered by tests. The time where growth was prioritized over reliability and stability is behind us. Therefore, renaming CSS class names for debugging purposes might seem outdated.
Yet, 4 years after I wrote this code, we are still using it and we still see values in it.
For example, onboarding new frontend developers is easier. If an element has a class that starts with _grapes than they know it’s coming from Grapes, the design system.
Another hidden gem is that it prevents people from overriding the style of our components. You might have already understood this with the code above, but the generation of these CSS class names is not stable. Each build will produce a different class name for a given set of CSS rules.
/* A simple example */
.container {
display: block;
}
/* Becomes in build #1 */
._grapes-ah {
display: block;
}
/* But becomes in build #2 */
._grapes-5f {
display: block;
}
At first, we didn’t give it much thought, but it proved to be a significant advantage when Spendesk needed to leverage the design system for complete UI change. The absence of override over our CSS means we could safely change our styles without fear of breaking the main application. Again, that saved us countless hours of QA.