JSPM

  • Created
  • Published
  • Downloads 173
  • Score
    100M100P100Q89032F
  • License MIT

Generate scss/sass from your design tokens schema (requires @cobalt-ui/cli)

Package Exports

  • @cobalt-ui/plugin-sass
  • @cobalt-ui/plugin-sass/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@cobalt-ui/plugin-sass) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@cobalt-ui/plugin-sass

Generate Sass output for Cobalt from design tokens.

Setup

npm i -D @cobalt-ui/plugin-sass
// tokens.config.mjs
import pluginSass from '@cobalt-ui/plugin-sass';

/** @type import('@cobalt-ui/core').Config */
export default {
  plugins: [
    pluginSass({
      /** set the filename inside outDir */
      filename: './index.scss',
      /** output CSS vars generated by @cobalt-ui/plugin-css? */
      pluginCSS: undefined,
      /** use indented syntax? (.sass format) */
      indentedSyntax: false,
      /** embed file tokens? */
      embedFiles: false,
      /** handle specific token types */
      transform(token, mode) {
        // Replace "sans-serif" with "Brand Sans" for font tokens
        if (token.$type === 'fontFamily') {
          return token.$value.replace('sans-serif', 'Brand Sans');
        }
      },
    }),
  ],
};

Usage

Tokens

Use the provided token() function to get a token by its ID (separated by dots):

@use '../tokens' as *; // update '../tokens' to match your location of tokens/index.scss

.heading {
  color: token('color.blue');
  font-size: token('typography.size.xxl');
}

Note that a function has a few advantages over plain Sass variables:

  • ✅ The name perfectly matches your schema (no guessing!)
  • ✅ You can programmatically pull values (which is more difficult to do with Sass vars)
  • ✅ Use the same function to access modes

By default, the token() function returns the raw token value to Sass. But to take advantage of all the features of plugin-css such as built-in color theming and out-of-the-box P3 support, you’ll want to use both together.

In your tokens.config.mjs file, opt in by adding a pluginCSS option. That will automatically load @cobalt-ui/plugin-css and will pass all options to it (all options are supported):

// tokens.config.mjs
import pluginSass from '@cobalt-ui/plugin-sass';

/** @type import('@cobalt-ui/core').Config */
export default {
  plugins: [
    pluginSass({
      cssVars: true,
      pluginCSS: {
        prefix: 'ds',
        modeSelectors: {
          'color#light': ['[data-color-theme="light"]'],
          'color#dark': ['[data-color-theme="dark"]'],
        },
      },
    }),
  ],
};

⚠️ Don’t load another instance of @cobalt-ui/plugin-css, otherwise they may conflict!

Lastly, you’ll need to make sure the new tokens.css file is loaded in your app somehow (otherwise the variables won’t be defined):

  // src/app.ts
+ import '../tokens/tokens.css';

Now token('color.blue') will output var(--ds-color-blue) instead of a raw hex value, and your app can now dynamically change themes and take advantage of all the power CSS variables have to offer.

If you‘re using Sass, this method is recommended because token() will safely guard against accidental typos for your design tokens.

Typography

As $type: "typography" tokens contain multiple values, you’ll need to use the typography() mixin instead:

@use '../tokens' as *;

.heading {
  @include typography('typography');

  font-size: token('typography.size.xxl');
}

Note that you can override any individual property so long as it comes after the mixin.

Modes

If you take advantage of modes in your tokens, you can pass a 2nd param into tokens() with a mode name:

@use '../tokens' as *;

.heading {
  color: token('color.blue');

  body[color-mode='dark'] & {
    color: token('color.blue', 'dark');
  }
}

⚠️ Note that modes are designed to gracefully fall back. So if a certain value isn’t defined on a mode, it will fall back to the default, rather than throwing an error.

List modes

To see which modes a token has defined, use the getModes() function which returns a list. This can be used to generate styles for specific modes:

@use '../tokens' as *;

@for $mode in listModes('color.blue') {
  [data-color-mode='#{$mode}'] {
    color: token('color.blue', $mode);
  }
}

Config

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: token('icon.alert');
}

// Becomes …
.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: token('icon.alert');
}

// Becomes …
.icon-alert {
  background-image: url('image/svg+xml;utf8,<svg …></svg>');
}

Read more

Transform

Inside plugin options, you can specify an optional transform() function:

/** @type import('@cobalt-ui/core').Config */
export default {
  plugins: [
    pluginSass({
      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():

/** @type import('@cobalt-ui/core').Config */
export default {
  plugins: [
    pluginSass({
      transform(token, mode) {
        switch (token.$type) {
          case 'my-custom-type': {
            return String(token.$value);
            break;
          }
        }
      },
    }),
  ],
};