I have listed here the issue I am trying to solve.
Convert following from
{"component": {
"button": {
"padding": { "value": "{size.padding.medium.value}" },
"font-size": { "value": 2 },
"text-align": { "value": "center" },
"primary": {
"modifier": "true",
"background-color": { "value": "hsl(10, 80, 50)" },
"color": { "value": "{color.font.inverse.value}" }
},
"secondary": {
"modifier": "true",
"background-color": { "value": "{color.background.primary.value}" },
"color": { "value": "{color.font.link.value}" }
}
}
}
}
to
.component-button {
padding: " ";
font-size: " ";
& .primary {
background-color: "",
color: ""
}
}
(I posted an answer to your Stack Overflow question but thought I鈥檇 cross-post it here too.)
The way I would do this is create a custom format and template. I would also move all of the tokens that you want to be transformed out of the component category and use pointers instead. I did this for the sample button.json token file used to generate the final file.
The files needed to run the example are below.
{
"color": {
"background": {
"primary": { "value": "hsl(10, 80, 50)" },
"secondary": { "value": "hsl(10, 80, 90)" }
},
"font": {
"inverse": { "value": "#fff" },
"link": { "value": "#00f" }
}
},
"size": {
"2": { "value": 16 },
"padding": {
"medium": { "value": 16 }
}
},
"component": {
"button": {
"padding": { "value": "{size.padding.medium.value}" },
"font-size": { "value": "{size.2.value}" },
"text-align": { "value": "center" },
"primary": {
"background-color": { "value": "{color.background.primary.value}" },
"color": { "value": "{color.font.inverse.value}" }
},
"secondary": {
"background-color": { "value": "{color.background.secondary.value}" },
"color": { "value": "{color.font.link.value}" }
}
}
}
}
const _ = require('lodash')
/*
This is formatted oddly in order to get a nice final shape
in `button.scss`.
Essentially what the template is doing is looping through
the props object and outputting the top-level properties
as the parent classnames, then for each child props of
"classname" it looks to see if the child prop is an object,
if it is, then it outputs the Sass `&.` operator with the
child prop rule as the sub classname and then each child
prop of the value as the final CSS style rule and value.
If it's not an object then it outputs the rule and value
as the CSS style rule and value.
*/
const template = _.template(`<% _.each(props, function(prop, classname) { %>.<%= classname %> {
<% _.each(prop, (value, rule) => { %><% if (typeof value === 'object') { %> &.<%= rule %> {<% _.each(value, (subvalue, subrule) => { %>
<%= subrule %>: <%= subvalue %>;<% }) %>
}<% } else { %> <%= rule %>: <%= value %>;<% } %>
<% }) %><% }) %>}
`)
const StyleDictionary = require('style-dictionary')
.registerFormat({
name: 'scss/button',
formatter: function(dictionary, config) {
/*
allProperties is an array containing all the matched
tokens based on the filter.
*/
const { allProperties } = dictionary
/*
Set up an empty object to hold the final shape to pass
to the custom template.
After the allProperties.map(), props will look like this:
{
'component-button': {
padding: '16px',
'font-size': '16px',
'text-align': 'center',
primary: { 'background-color': '#e63c19', color: '#ffffff' },
secondary: { 'background-color': '#fad8d1', color: '#0000ff' }
}
}
*/
const props = {}
// go through properties and structure final props object
allProperties.map(prop => {
/*
Extract the attributes object created by the 'attribute/cti'
transform and the transformed token value.
*/
const { attributes, value } = prop
// extract attributes to build custom class and style rules
const { category, type, item, subitem } = attributes
// build main classname for .scss file
const classname = `${category}-${type}`
/*
Add to the props object if it doesn't already exist.
We run the check to see if the classname exists already as an
object property because in our case, `classname` will be the
same for each token object in allProperties because each token
is under the same category and type.
*/
if (!props.hasOwnProperty(classname)) {
props[classname] = {}
}
/*
If the token object has a subitem, use the item as the subclass.
Run the same check to see if this particular subclass (item) has
been added yet.
*/
if (subitem) {
if (!props[classname].hasOwnProperty(item)) {
props[classname][item] = {}
}
// add the subitem and value as final CSS rule
props[classname][item][subitem] = value
}
else {
// add the item as a CSS rule, not a subclass
props[classname][item] = value
}
})
/*
Pass the final `props` object to our custom template to render
the contents for the final button.scss file.
*/
return template({ props })
}
})
.extend({
source: ['button.json'],
platforms: {
scss: {
buildPath: 'build/',
transforms: [
'attribute/cti', // setup attributes object
'color/css', // transform color values to hex
'name/cti/kebab', // prevent name collisions
'size/px' // transform size values to px
],
files: [
{
destination: 'button.scss',
format: 'scss/button',
filter: {
attributes: {
category: 'component',
type: 'button'
}
}
}
]
}
}
})
// run Style Dictionary
StyleDictionary.buildAllPlatforms()
I made a quick sample project if you wanted to test it out:
https://github.com/davidyeiser/style-dictionary-scss-component
After you run yarn build you should see a button.scss file in the _build_ directory that looks like this:
.component-button {
padding: 16px;
font-size: 16px;
text-align: center;
&.primary {
background-color: #e63c19;
color: #ffffff;
}
&.secondary {
background-color: #fad8d1;
color: #0000ff;
}
}
Let me know if you have any questions 鈥斅爃appy to answer them!
Thank you for posting this answer @davidyeiser! That is definitely a great solution. I have done something similar in some projects, but instead used a custom action to build the files. I made a quick example repo as well: https://github.com/dbanksdesign/style-dictionary-example-component-scss (though it is not as thoroughly commented as david's).
Nice! I like the modifier and child attributes.
@sreenu539 did these examples answer you question? Could we close this issue?
@dbanksdesign
It seems that style-dictionary is so powerful that it can fully replace sass.
In a design system, have u ever tried write all tokens and components in style dictionary, and then output .css files directly?
we can write tokens source files in js, to use the sass equivalent vars/functions/mixins/nested.
The tradeoff is to write a lot of formatters.
I never actually thought about that... I have not tried, or seen anyone try, to write all styling in style dictionary and output to CSS directly. But it is definitely possible. You are right, the trade-off would be writing a lot of custom code in the form of formatters potentially. But I could also see writing a small handful of generic formatters as long as you structure your style dictionary object in a specific way. Talking through this, it kind of sounds like CSS-in-JS, but reversed... JS-in-CSS? You write styles in JS and output plain CSS (as opposed to the way CSS-in-JS libraries work today)...

@dbanksdesign
It seems that style-dictionary is so powerful that it can fully replace sass.
In a design system, have u ever tried write all tokens and components in style dictionary, and then output.cssfiles directly?
we can write tokens source files in js, to use the sass equivalent vars/functions/mixins/nested.
The tradeoff is to write a lot of formatters.
This... really is mind-blowing. Something to think about more fully supporting in 4.0?
Most helpful comment
(I posted an answer to your Stack Overflow question but thought I鈥檇 cross-post it here too.)
The way I would do this is create a custom format and template. I would also move all of the tokens that you want to be transformed out of the
componentcategory and use pointers instead. I did this for the samplebutton.jsontoken file used to generate the final file.The files needed to run the example are below.
button.json
index.js
I made a quick sample project if you wanted to test it out:
https://github.com/davidyeiser/style-dictionary-scss-component
After you run
yarn buildyou should see abutton.scssfile in the _build_ directory that looks like this:Let me know if you have any questions 鈥斅爃appy to answer them!