@cobalt-ui/plugin-css
Generate .css
from your design tokens using Cobalt.
Features
- ✅ 🌈 Automatic P3 color enhancement
- ✅ Automatic mode inheritance (e.g. light/dark mode)
Setup
npm i -D @cobalt-ui/plugin-css
import pluginCSS from '@cobalt-ui/plugin-css';
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [pluginCSS()],
};
Generates:
:root {
--color-blue: #0969da;
--color-green: #2da44e;
--color-red: #cf222e;
--color-black: #101010;
--color-ui-text: var(--color-black);
}
You can then use these anywhere in your app.
Usage
Running npx co build
with the plugin set up will generate a tokens/tokens.css
file. Inspect that, and import where desired and use the CSS Custom Properties as desired (docs).
Options
All Options
Here are all plugin options, along with their default values
import pluginCSS from '@cobalt-ui/plugin-css';
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [
pluginCSS({
filename: './tokens.css',
modeSelectors: [
],
embedFiles: false,
transform: () => null,
prefix: '',
p3: true,
}),
],
};
Embed Files
Say you have link
tokens in your tokens.json
:
{
"icon": {
"alert": {
"$type": "link",
"$value": "./icon/alert.svg"
}
}
}
By default, consuming those will print values as-is:
.icon-alert {
background-image: var(--icon-alert);
}
.icon-alert {
background-image: url('./icon/alert.svg');
}
In some scenarios this is preferable, but in others, this may result in too many requests and may result in degraded performance. You can set embedFiles: true
to generate the following instead:
.icon-alert {
background-image: var(--icon-alert);
}
.icon-alert {
background-image: url('image/svg+xml;utf8,<svg …></svg>');
}
Read more
Mode Selectors
Example
To generate CSS for Modes, add a modeSelectors: []
array to your config, and specify mode: [selector1, selector2, …]
.
All mode names must start with the #
character. You can also optionally filter to a token group by adding part or all of a group name before the #
. For example, if your color.*
tokens had light
and dark
mode you wanted to generate CSS for, as well as transition.*
tokens with reduced
modes, you could add the following selectors:
import css from '@cobalt-ui/plugin-css';
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [
css({
modeSelectors: [
{
mode: 'light',
selectors: ['@media (prefers-color-scheme: light)', 'body[data-color-mode="light"]'],
tokens: ['color.*'],
},
{
mode: 'dark',
selectors: ['@media (prefers-color-scheme: dark)', 'body[data-color-mode="dark"]'],
tokens: 'color.*',
},
{
mode: 'reduced',
selectors: ['@media (prefers-reduced-motion)'],
},
],
}),
],
};
This would generate the following CSS:
:root {
}
@media (prefers-color-scheme: light) {
:root {
}
}
body[data-color-mode='light'] {
}
@media (prefers-color-scheme: dark) {
:root {
}
}
body[data-color-mode='dark'] {
}
@media (prefers-reduced-motion) {
:root {
}
}
By default you get automatic inference from the @media
selectors. But as a fallback, you could also manually set <body data-color-mode="[mode]">
to override the default (e.g. to respect user preference).
But more than just classes can be used (that’s why it’s called modeSelectors
and not modeClasses
)! You could also generate CSS if your type.size
group had desktop
and mobile
sizes:
import css from '@cobalt-ui/plugin-css';
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [
css({
modeSelectors: [
{mode: 'mobile', tokens: ['type.size.*'], selectors: ['@media (max-width: 600px)']},
{mode: 'desktop', tokens: ['type.size.*'], selectors: ['@media (min-width: 600px)']},
],
}),
],
};
That will generate the following:
:root {
}
@media (max-width: 600px) {
:root {
}
}
@media (min-width: 600px) {
:root {
}
}
Syntax
The #
character designates the mode. You must have a #
somewhere in the selector.
#light
: match any token that has a light
modecolor#light
: deeply match any token inside the color
group, that has a light
modecolor.base#light
: deeply match any token inside the color.base
group with a light
mode, but ignore any other tokens inside color
Further Reading
To learn about modes, read the documentation
Transform
Inside plugin options, you can specify an optional transform()
function.
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [
pluginCSS({
transform(token, mode) {
const oldFont = 'sans-serif';
const newFont = 'Custom Sans';
if (token.$type === 'fontFamily') {
return token.$value.map((value) => (value === oldFont ? newFont : value));
}
},
}),
],
};
Your transform will only take place if you return a truthy value, otherwise the default transformer will take place.
Custom tokens
If you have your own custom token type, e.g. my-custom-type
, you’ll have to handle it within transform()
:
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [
pluginCSS({
transform(token, mode) {
switch (token.$type) {
case 'my-custom-type': {
return String(token.$value);
break;
}
}
},
}),
],
};
Usage with @cobalt-ui/plugin-sass
If you’re using Sass in your project, you can load this plugin through @cobalt-ui/plugin-sass, which lets you use CSS vars while letting Sass typecheck everything and making sure your stylesheet references everything correctly.
To use this, replace this plugin with @cobalt-ui/plugin-sass in tokens.config.mjs
and pass all options into pluginCSS: {}
:
- import pluginCSS from '@cobalt-ui/plugin-css';
+ import pluginSass from '@cobalt-ui/plugin-sass';
/** @type import('@cobalt-ui/core').Config */
export default {
tokens: './tokens.json',
outDir: './tokens/',
plugins: [
- pluginCSS({ filename: 'tokens.css }),
+ pluginSass({
+ pluginCSS: { filename: 'tokens.css' },
+ }),
],
};
This changes token('color.blue')
to return CSS vars rather than the original values. To learn more, read the dos.