Table of Contents
1 | Introduction to Design Tokens
2 | Managing and Exporting Design Tokens With Style Dictionary
3 | Exporting Design Tokens From Figma With Style Dictionary
4 | Consuming Design Tokens From Style Dictionary Across Platform-Specific Applications
5 | Generating Design Token Theme Shades With Style Dictionary
6 | Documenting Design Tokens With Docusaurus
7 | Integrating Design Tokens With Tailwind
8 | Transferring High Fidelity From a Design File to Style Dictionary
9 | Scoring Design Tokens Adoption With OCLIF and PostCSS
10 | Bootstrap UI Components With Design Tokens And Headless UI
11 | Linting Design Tokens With Stylelint
12 | Stitching Styles to a Headless UI Using Design Tokens and Twind
What You’re Getting Into
In the previous article, I wrote about my process to create a CLI that generates a “scorecard” rating an application’s adoption of design tokens.
This tooling is useful to make sure that applications within a company are actually using the design tokens that are available to them.
A scorecard is a nice tool for acute, or short-term, testing. It would work well when applications in a company adopt new design tokens following an initiative.
It can be useful for chronic, long-term testing (i.e. manual testing that a developer and/or QA tester does before releasing new code).
However, catching the implementation of unofficial design tokens during local development is a better experience for the developer and more likely to prevent the release of unofficial design specifications.
Just as an EsLint plugin works well to catch improper syntax before a manual code review, so too, linting to catch the implementation of unofficial design tokens can lighten the load of manual testing.
A tool called Stylelint offers the ability to lint your CSS.
What if there was a Stylelint plugin that could detect the implementation of unofficial design tokens/specs when given the official set of tokens?
In this brief article, I’ll show how I ventured to build such a plugin.
The Process
To start, I read through the developer’s guide on writing Stylelint plugins.
Then, I looked through the source code of existing plugins that I found on the awesome-stylelint repo.
This helped generate the boilerplate code for a Stylelint plugin.
Ultimately, the Stylelint plugin consists of rules that can be enabled by a user.
Each rule is a function that takes in the user’s options for a rule which
they configure. You have to validate these options in the code using a
utils.validateOptions
util that the Stylelint package
provides for you. Also, you should document these options in your
documentation.
I wrote the function for an official-specs
rule that expects
the user to configure/provide a set of official design tokens/specs:
import scorecard from '@tempera/postcss-scorecard';
import { utils } from 'stylelint';
import { namespace, validateSpecs } from '../utils';
export default {
'official-specs': function rule(specs = {}) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, {
actual: specs,
// validateSpecs is a function
// that validates that
// the provided specs is a flat
// object consisting of key-value pairs
possible: [validateSpecs]
});
if (!validOptions) {
return null;
}
};
};
Next, I used the @tempera/postscss-scorecard plugin which I created for a “scorecard” reporting tool as an API.
This plugin exposes hooks into the processing of CSS to do something when a valid or invalid specification is found:
const scorecard = require("@tempera/postcss-scorecard");
const specs = require("./tokens");
await postcss()
.use(
scorecard({
onInvalid: (score) => {
// do something when CSS property is not an official spec
},
onValid: (score) => {
// do something when CSS property is an official spec
},
onFinished: () => {
// do something after validation finishes
},
specs, // the official design tokens
})
)
.process(css, { from: undefined });
It also provides a score
context which contains the
prop
, value
, as well as the predicted
nearestValue
(the closest official value to the unofficial
value).
Using this as an API in the Stylelint rule, messages can be reported when
a CSS declaration is using an invalid/unofficial value by using the
utils.report
method.
The utils.report
method expects a rule message that can be
generated using the utils.ruleMessages
method.
I exposed a rule message that receives the actual/unofficial spec and the
nearest official spec from the score
context.
import scorecard from '@tempera/postcss-scorecard';
import { utils } from 'stylelint';
import { namespace, validateSpecs } from '../utils';
export const ruleName = namespace('official-specs');
export const messages = utils.ruleMessages(ruleName, {
'official-specs': ({ spec, nearestSpec }) => {
const prefix = `An unofficial spec was detected: "${spec}"`;
if (!nearestSpec) {
return prefix;
}
return `${prefix}. The nearest official spec is ${nearestSpec}`;
}
});
export default {
'official-specs': function rule(specs = {}) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, {
actual: specs,
possible: [validateSpecs]
});
if (!validOptions) {
return null;
}
const { Once } = scorecard({
onInvalid: (score) => {
const spec = score.value;
const nearestSpec = score.nearestValue;
const node = score.context;
utils.report({ message: messages['official-specs']({ spec, nearestSpec }), node, result, ruleName })
},
specs,
});
Once(root);
};
};
Just like that, we can provide linting around the usage of unofficial design specifications in CSS:
🎉 Cool!
Final plugin: https://github.com/michaelmang/tempera/tree/master/packages/stylelint
Conclusion
I hope this experimental plugin helps to highlight the potential to enforce the usage design tokens.
I hope it also helps to stimulate creativity for future tooling to unleash the power and success of adopting design tokens.