Style-dictionary: How to deal with opacity, shadow and other tokens for iOS and Android?

Created on 27 Aug 2020  路  5Comments  路  Source: amzn/style-dictionary

Hello!

We have opacity tokens in our Design System. To generate CSS and SCSS variables its's ok, but how can we deal with opacity tokens for iOS and Android?

Example JSON

{
  "opacity": {
    "8": { "value": ".8" },
    "6": { "value": ".6" },
    "4": { "value": ".4" },
    "1": { "value": ".16" }
  }
}
````

Generates
```scss
$opacity-1: .16;
$opacity-4: .4;
$opacity-6: .6;
$opacity-8: .8;

Or shadow tokens

{
  "shadow": {
    "level-1": { "value": "0 4px 8px 0 rgba (0, 0, 0, 0.10)" },
    "level-2": { "value": "0 8px 16px 0 rgba (0, 0, 0, 0.10)" },
    "level-3": { "value": "0 16px 32px 0 rgba (0, 0, 0, 0.10)" },
    "level-4": { "value": "0 32px 64px 0 rgba (0, 0, 0, 0.10)" }
  }
}

That generates

$shadow-level-1: 0 4px 8px 0 rgba (0, 0, 0, 0.10);
$shadow-level-2: 0 8px 16px 0 rgba (0, 0, 0, 0.10);
$shadow-level-3: 0 16px 32px 0 rgba (0, 0, 0, 0.10);
$shadow-level-4: 0 32px 64px 0 rgba (0, 0, 0, 0.10);
integration question

Most helpful comment

Here's the custom shadow transformer I wrote for our token project:

// configuration/shadowTransform.js 
const styleDictionary = require("style-dictionary");
const tinycolor = require("tinycolor2");

const toPx = (value) =>
  styleDictionary.transform["size/remToPx"].transformer({ value });

const shadowMatcher = (prop) => prop.attributes.category === "shadow";

const webShadowTransformer = (prop) => {
  const {
    blurRadius,
    color,
    offsetX,
    offsetY,
    spreadRadius,
  } = prop.original.value;

  return `${toPx(offsetX)} ${toPx(offsetY)} ${toPx(blurRadius)} ${toPx(
    spreadRadius
  )} ${tinycolor(color).toRgbString()}`;
};

module.exports.shadowCSSTransform = {
  matcher: shadowMatcher,
  name: "shadow/css",
  transformer: webShadowTransformer,
  type: "value",
};

module.exports.shadowSCSSTransform = {
  matcher: shadowMatcher,
  name: "shadow/scss",
  transformer: webShadowTransformer,
  type: "value",
};

These get plugged into the project's config, like so:

// configuration/index.js
module.exports = {
  transform: {
    "shadow/css": shadowCSSTransform,
    "shadow/scss": shadowSCSSTransform,
  },
  // ...
}

鈥nd individual config.platforms use them. Seems to work pretty well (for web).

All 5 comments

up 馃槉

For more complex token like shadows or gradients I typically store the tokens as an array or object and then write custom transforms depending on which platform you need to support.

To take your shadows for example you could store the tokens like so:

{
  "shadow": {
    "level-1": {
      "value": {
        "x": 0,
        "y": 4,
        "blur": 8,
        "spread": 0,
        "color": "#000000",
        "opacity": 0.1
      }
    },
    "level-2": "..."
  }
}

And then your custom transforms can access the individual values as needed. For the web it would look something like this (haven't actually tested):

const tinycolor = require('tinycolor2')

StyleDictionary.registerTransform({
  name: 'shadow/scss',
  type: 'value',
  matcher: function(prop) {
    return prop.attributes.category === 'shadow';
  },
  transformer: function(prop) {
    // destructure shadow values from original token value
    const {
      x,
      y,
      blur,
      spread,
      color,
      alpha
    } = prop.original.value

    // convert hex code to rgba string
    const shadowColor = tinycolor(color)
    shadowColor.setAlpha(alpha)
    shadowColor.toRgbString()

    return `${x}px ${y}px ${blur}px ${spread}px ${shadowColor}`
  }
});

For Android/iOS it would be a similar pattern though you may need to look into custom formats and templates for those depending on what the desired outcome looks like.

Here's the custom shadow transformer I wrote for our token project:

// configuration/shadowTransform.js 
const styleDictionary = require("style-dictionary");
const tinycolor = require("tinycolor2");

const toPx = (value) =>
  styleDictionary.transform["size/remToPx"].transformer({ value });

const shadowMatcher = (prop) => prop.attributes.category === "shadow";

const webShadowTransformer = (prop) => {
  const {
    blurRadius,
    color,
    offsetX,
    offsetY,
    spreadRadius,
  } = prop.original.value;

  return `${toPx(offsetX)} ${toPx(offsetY)} ${toPx(blurRadius)} ${toPx(
    spreadRadius
  )} ${tinycolor(color).toRgbString()}`;
};

module.exports.shadowCSSTransform = {
  matcher: shadowMatcher,
  name: "shadow/css",
  transformer: webShadowTransformer,
  type: "value",
};

module.exports.shadowSCSSTransform = {
  matcher: shadowMatcher,
  name: "shadow/scss",
  transformer: webShadowTransformer,
  type: "value",
};

These get plugged into the project's config, like so:

// configuration/index.js
module.exports = {
  transform: {
    "shadow/css": shadowCSSTransform,
    "shadow/scss": shadowSCSSTransform,
  },
  // ...
}

鈥nd individual config.platforms use them. Seems to work pretty well (for web).

Thanks @davidyeiser your solition works fine to me!

@swashcap Does the webShadowTransformer you wrote work for referencing existing values?

Like this e.g.:

"value": {
    "blurRadius": 4,
    "color": "{color.base.gray.900}",
    "offsetX": 1,
    "offsetY": 4,
    "spreadRadius": 0,
}

I tried it and get this error when I run it.

  value.replace(regex, function(match, variable) {
        ^

TypeError: value.replace is not a function
    at Object.getReferences (/Users/.../.../.../.../node_modules/style-dictionary/lib/utils/references/getReferences.js:45:9)

I'm just curious if the transform doesn't support it, or if I have made a mistake somewhere else 馃槉

Was this page helpful?
0 / 5 - 0 ratings

Related issues

custa1200 picture custa1200  路  5Comments

chazzmoney picture chazzmoney  路  4Comments

clepore picture clepore  路  5Comments

gael-boyenval picture gael-boyenval  路  3Comments

nategreen picture nategreen  路  5Comments