Style-dictionary: I can't generate variables css to a specific class

Created on 17 Jul 2020  ยท  13Comments  ยท  Source: amzn/style-dictionary

Hello!

We are use the Style Dictionary to generate our tokens, but we have a problem here.

The tokens are building to different products (themes) and we like use variables css, but in the generated files, we got:

// theme-x.css
:root {
   // variables
}

// theme-y.css
:root {
   // variables
}

How change the destiny of variables css to custom class, like a:

// theme-x.css
.theme-x {
   // variables
}

// theme-y.css
.theme-y {
   // variables
}

๐Ÿป

Most helpful comment

Unfortunately the built-in css/variables format does not allow this. But that would be a good addition! Here is the code:
https://github.com/amzn/style-dictionary/blob/master/lib/common/formats.js#L106

You could write a custom format that does this too if you can't wait for that change to be made into the core library:

StyleDictionary.registerFormat({
  name: 'css/variables',
  formatter: function(dictionary, config) {
    return `${this.selector} {
${dictionary.allProperties.map(prop => `  --${prop.name}: ${prop.value};`)}
    }`
  }
});

StyleDictionary.extend({
  source: [],
  platforms: {
    css: {
      files: [{
        destination: 'theme-x.css',
        format: 'css/variables',
        selector: '.theme-x'
      }]
    }
  }
})

Note: this is untested code, but should probably work and doesn't include full working code. Also it won't exactly match the functionality of 'css/variables' format, but if you want to you could copy the functions in https://github.com/amzn/style-dictionary/blob/main/lib/common/formats.js to make it work exactly the same.

Let me know if this helps!

All 13 comments

Unfortunately the built-in css/variables format does not allow this. But that would be a good addition! Here is the code:
https://github.com/amzn/style-dictionary/blob/master/lib/common/formats.js#L106

You could write a custom format that does this too if you can't wait for that change to be made into the core library:

StyleDictionary.registerFormat({
  name: 'css/variables',
  formatter: function(dictionary, config) {
    return `${this.selector} {
${dictionary.allProperties.map(prop => `  --${prop.name}: ${prop.value};`)}
    }`
  }
});

StyleDictionary.extend({
  source: [],
  platforms: {
    css: {
      files: [{
        destination: 'theme-x.css',
        format: 'css/variables',
        selector: '.theme-x'
      }]
    }
  }
})

Note: this is untested code, but should probably work and doesn't include full working code. Also it won't exactly match the functionality of 'css/variables' format, but if you want to you could copy the functions in https://github.com/amzn/style-dictionary/blob/main/lib/common/formats.js to make it work exactly the same.

Let me know if this helps!

Thank you @dbanksdesign! You've saved the day! ๐Ÿ˜๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘

@dbanksdesign Thank you so mutch for your help! โค๏ธ

We understand about the registerFormat method and got a great result. Just a observation to your script. The method .map return a new array with ,. We use the join() method to solve this and get a right css on finish.

Script:

StyleDictionaryPackage.registerFormat({
  name: 'css/variables',
  formatter: function(dictionary, config) {
    return `${this.selector} {
${dictionary.allProperties.map(prop => `  --${prop.name}: ${prop.value};`).join('\n')}
    }`
  }
});

@gabrielmedina great catch on the .join() my bad! ๐Ÿคฆโ€โ™‚๏ธ That is why I need to test my code before sending it to people.

Man, you helped us so much. The .join() is a detail ;)

Hi @gabrielmedina sorry to ask but how did you setup the style-dictionary to generate the 2 separate themes? do you mind sharing?

Hi @sarah-martinellibenedetti

This is my build.js

const StyleDictionaryPackage = require('style-dictionary');

const path = 'src/tokens'

function getStyleDictionaryConfig(brand, platform) {
  return {
    source: [
      `${path}/properties/brands/${brand}/*.json`,
      `${path}/properties/globals/**/*.json`,
      `${path}/properties/platforms/${platform}/**/*.json`
    ],
    platforms: {
      web: {
        transformGroup: 'web',
        buildPath: `${path}/build/web/`,
        files: [
          {
            destination: '_tokens-globais.scss',
            format: 'css/variables',
            selector: ':root',
            filter: { type: 'global' }
          },
          {
            destination: `_tokens-${brand}.scss`,
            format: 'css/variables',
            selector: `.tokens-${brand}`,
            filter: { type: 'brand' }
          }
        ]
      },
      android: {
        transformGroup: 'android',
        buildPath: `${path}/build/android/`,
        files: [
          {
            destination: `tokens-${brand}.colors.xml`,
            format: 'android/colors'
          },
          {
            destination: `tokens-${brand}.dimens.xml`,
            format: 'android/dimens'
          },
          {
            destination: `tokens-${brand}.font_dimens.xml`,
            format: 'android/fontDimens'
          }
        ]
      },
      ios: {
        transformGroup: 'ios',
        buildPath: `${path}/build/ios/`,
        files: [
          {
            destination: `tokens-${brand}.h`,
            format: 'ios/macros'
          },
        ]
      }
    }
  };
}

StyleDictionaryPackage.registerFormat({
  name: 'css/variables',
  formatter: function (dictionary, config) {
    return `${this.selector} {
      ${dictionary.allProperties.map(prop => `  --${prop.name}: ${prop.value};`).join('\n')}
    }`
  }
});

console.log('Build started...');

const brands = ['brand1', 'brand2', 'brand3']
const plataforms = ['web', 'ios', 'android']

brands.map(function (brand) {
  plataforms.map(function (platform) {
    console.log('\n==============================================');
    console.log(`\nProcessing: [${platform}] [${brand}]`);

    const StyleDictionary = StyleDictionaryPackage.extend(getStyleDictionaryConfig(brand, platform);
    StyleDictionary.buildPlatform(platform);

    console.log('\nEnd processing');
  });
});

console.log('\n==============================================');
console.log('\nBuild completed!');

@gabrielmedina understood! Do you have your project in some repository? Can I please take a look? I just want to understand better how did you organize the tokens inside each brand/platform. Thank you for your patience!

@sarah-martinellibenedetti Sorry, us project don't is open source ๐Ÿ˜ข

But, I go try explain to you:

folders

/properties
    /brands
        /brand1
            brand1.json
        /brand2
            brand2.json
        /brand3
            brand3.json

    /globals
        border.json
        font.json
        height.json
        z-index.json

    /platforms
        /android
            button.json
            font.json
        /ios
            button.json
            font.json
        /web
            breakpoint.json
            font.json

brands/brand1.json

{
    "color": {
        "primary": {
            "base": { "value": "#27bd6e", "type": "brand" },
            "accessible": { "value": "#036c36", "type": "brand" }
        },
        "secondary": {
            "base": { "value": "#128dc4", "type": "brand" }
        }
    }
}

plataforms/web/font.json

{
    "font": {
        "family": { "value": "'Open Sans', sans-serif", "type": "global" },
        "weight": {
            "regular": { "value": "400", "type": "global" },
            "semibold": { "value": "600", "type": "global" },
            "bold": { "value": "700", "type": "global" }
        }
    }
}

@sarah-martinellibenedetti It looks like @gabrielmedina followed this example: https://github.com/amzn/style-dictionary/tree/main/examples/advanced/multi-brand-multi-platform hopefully that helps!

@dbanksdesign @gabrielmedina

in v3,if you use registerFormat, you lose the benefits of outputReferences.
Is there any good idea for custom top level selector name rather than the default :root?

what I can come up with is to create custom action scripts to modify the .css file after tokens are built.

@uptonking you could actually use outputReferences in a custom format. Take a look at this example:

https://github.com/amzn/style-dictionary/blob/3.0/examples/advanced/variables-in-outputs/sd.config.js

The custom format doesn't use the outputReferences option, which is my bad, it will always output references. But you could change the formatter code to this to check if the outputReferences option is true:

function({dictionary, options}) {
      return dictionary.allProperties.map(token => {
        let value = JSON.stringify(token.value);
        // the `dictionary` object now has `usesReference()` and
        // `getReference()` methods. `usesReference()` will return true if
        // the value has a reference in it. `getReference()` will return
        // the reference to the whole token so that you can access its
        // name or any other attributes.
        if (options.outputReferences) {
          if (dictionary.usesReference(token.original.value)) {
            const reference = dictionary.getReference(token.original.value);
            value = reference.name;
          }
        }
        return `export const ${token.name} = ${value};`
      }).join(`\n`)
    }

@dbanksdesign
I think it's unnecessary to use JSON.stringify(token.value);, it will output extra quotes.

I found a way to support css variables fallback values.

StyleDictionary.registerFormat({
  name: 'css/variables',
  formatter: function ({ dictionary, options }) {
    return `${this.selectorName} {
      ${dictionary.allProperties
        .map((prop) => {
          // let value = JSON.stringify(prop.value);
          let value = prop.value;

          if (options.outputReferences) {
            if (dictionary.usesReference(prop.original.value)) {
              const reference = dictionary.getReference(prop.original.value);

              value = reference.name;
              return `  --${prop.name}: var(--${value}, ${prop.value});`;
            }
          }
          return `  --${prop.name}: ${prop.value};`;
        })
        .join('\n')}
    }`;
  },
});

A flaw is that the indentation of the first two lines is unfriendly.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dbanksdesign picture dbanksdesign  ยท  5Comments

sarah-martinellibenedetti picture sarah-martinellibenedetti  ยท  4Comments

chazzmoney picture chazzmoney  ยท  4Comments

limitlessloop picture limitlessloop  ยท  4Comments

custa1200 picture custa1200  ยท  5Comments