A tool for experimenting with color and typographic tokens and generating output for use in coded implementations.
đźš§ This is a work-in-progress project. See the Todos list for what's done and what's to come.
This tool uses Style Dictionary to generate tokens in target formats from token files. This is similar to the Style dictionary Playground but with the addition of an interface to generate the base tokens.
The tokens
directory contains files that are compiled with tsc
then processed by Style Dictionary to generate the target output (currently SCSS).
Style Dictionary is configured to pick up any file matching dist/tokens/**/*.tokens.js
.
Each category of tokens uses helper functions to generate the tokens file from a configuration.
createTypeScale(options)
- Generates an array of font sizes from a set of optionscreateTypeScaleTokens(typeScale)
- Generates the typescale tokens input for use by Style DictionarycreateColorProgressionTokens(options)
- Generates the color tokens input for use by Style Dictionary
A TypeScaleOptions
object
interface TypeScaleOptions = {
baseSize: number
factor: number
minSize?: number
maxSize?: number
roundBeforeInterval?: number
roundAfterInterval?: number
}
The font size used as the base for the typescale calculations.
16
The baseSize
is not necessarily the first size in the progression. You can have a minSize
lower than the baseSize
which will result in typescale sizes lower than the baseSize
.
The baseSize
will always be within the typescale.
The amount to scale each level within the typescale.
1.125
The factor
is use to calculate the next font size in the typescale.
nextFontSize = previousFontSize * factor
The smallest allowable font size in the typescale.
12
The minSize
is not guaranteed to be in the final typescale. The factor
and roundBeforeInterval
may cause it to not be included.
// This config will not include the minSize in the typescale
{
baseSize: 20,
factor: 1.25,
minSize: 12,
maxSize: 72,
roundBeforeInterval: 8,
roundAfterInterval: 8
}
// results in typescale
[16, 20, 24, 32, 40, 48, 64]
The largest allowable font size in the typescale.
96
The maxSize
is not guaranteed to be in the final typescale. The factor
and roundAfterInterval
may cause it to not be included.
In the example above for minSize
, the maxSize
is not included in final typescale.
The interval between sizes below the base size.
4
When the factor
is applied, it will likely generate a float. For example,
// This config
{
baseSize: 16,
factor: 1.25,
minSize: 12,
maxSize: 72,
roundBeforeInterval: 4,
roundAfterInterval: 8
}
// returns this typescale without rounding
[
// 4 values prior
11.24, 12.64, 14.22,
// baseSize
16,
// 13 values prior
18, 20.25, 22.78, 25.63,
28.83, 32.43, 36.48, 41.04,
46.17, 51.94, 58.43, 65.73,
73.95
]
Rounding is applied to give us whole integer font sizes. roundBeforeInterval
controls the rounding of numbers prior to the baseSize
. After rounding, the final typescale is shorter:
[
// 1 value prior
12,
// baseSize
16,
// 7 values after
24, 32, 40, 48, 56, 64, 72
]
The interval between sizes above the base size.
4
roundBeforeInterval
controls the rounding of numbers prior to the baseSize
.
See the example above in roundBeforeInterval
to see how rounding is applied.
number[]
// Using the default options, returns
[12, 16, 20, 24, 28, 32, 36, 40, 48, 52, 60, 64, 72, 84, 92]
number[]
A tokens object
// Using the default options, returns
{
size: {
'12': { value: '0.75rem' },
'16': { value: '1rem' },
'20': { value: '1.25rem' },
'24': { value: '1.5rem' },
'28': { value: '1.75rem' },
'32': { value: '2rem' },
'36': { value: '2.25rem' },
'40': { value: '2.5rem' },
'48': { value: '3rem' },
'52': { value: '3.25rem' },
'60': { value: '3.75rem' },
'64': { value: '4rem' },
'72': { value: '4.5rem' },
'84': { value: '5.25rem' },
'92': { value: '5.75rem' },
base: { value: '{font.size.16}' }
}
}
This function uses the tinycolor2
package to apply color adjustments and generate new colors in a progression.
A BaseColorsOptions
object
type ColorAdjustment =
| number
| number[]
| ((levels: number[], index: number, options: BaseColorsOptions) => number)
type BaseColorsOptions = {
baseColor: string
baseColorIndex: number
levelsCount: number
baseColorKey?: string
startLevel?: number
levelGap?: number
lightdark?: ColorAdjustment
saturate?: ColorAdjustment
desaturate?: ColorAdjustment
spin?: ColorAdjustment
greyscale?: boolean
tokens?: TokenDictionary
}
The color used as the base for the color progression.
Accepted colors: hex, 8-digit hex, rgb(a), hsl(a), hsv(a), or named color. rgb(a), hsl(a), hsv(a) accept object input.
The position of the baseColor in the final progresson.
The number of colors in the progression.
A key to use for the base color in place of the key generated from the level. For example,
export const color = {
neutral: createColorProgressionTokens({
baseColor: '#4b5055',
baseColorIndex: 6,
baseColorKey: 'dark',
levelsCount: 7,
lightdark: [64, 56, 48, 24, 16, 8, 0],
}),
}
// returns
{
color: {
neutral: {
'100': { value: '#f2f3f4' },
'200': { value: '#dddfe1' },
'300': { value: '#c7cace' },
'400': { value: '#868d94' },
'500': { value: '#717980' },
'600': { value: '#5e646b' },
'dark': { value: '#4b5055' }
}
}
}
The color level at which the progression starts.
100
The numeric gap between color levels in the progression.
100
export const color = {
neutral: createColorProgressionTokens({
baseColor: '#4b5055',
baseColorIndex: 6,
levelGap: 10,
startLevel: 10,
levelsCount: 7,
lightdark: [64, 56, 48, 24, 16, 8, 0],
}),
}
// returns
{
color: {
neutral: {
'10': { value: '#f2f3f4' },
'20': { value: '#dddfe1' },
'30': { value: '#c7cace' },
'40': { value: '#868d94' },
'50': { value: '#717980' },
'60': { value: '#5e646b' },
'70': { value: '#4b5055' }
}
}
}
The light/dark color adjustment to apply between levels. Can be a number from 0 to 100.
0
(no adjustment)
Lighten and darken transformations are mutually exclusive. Otherwise they would cancel each other out. Lightening is only applied when the color's index is less than the baseColorIndex
. Darkening is only applied when the color's index is greater than the baseColorIndex
. The base color never gets transformed.
A number >= 100 will produce white for lightening and black for darkening.
number
The adjustment is applied evenly throughout the progression.
number[]
The number sharing the same index as the color level is used as the adjustment.
(levels, index, options) => number
A function that returns a number. Function includes parameters
- levels:
number[]
- The calculated level number, eg.[100, 200, 300]
- index:
number
- The index of the level being adjusted. - options:
BaseColorOptions
- The options object passed tocreateColorProgressionTokens
The saturation color adjustment to be applied between levels. Can be a number from 0 to 100.
0
(no saturation)
A number >= 100 will produce a fully saturated color.
The desaturation color adjustment to be applied between levels. Can be a number from 0 to 100.
0
(no desaturation)
A number >= 100 will produce a fully desaturated greyscale color.
The hue adjustment to be applied between levels. Can be a number from -360 to 360.
0
(no hue adjustment)
The numbers 0, -360, and 360 will result in no hue adjustment being applied to the color level.
A boolean that, when set as true
, will make all colors on the progression greyscale.
false
When true
, greyscale-ing is applied after all other color adjustments and will replace the desaturate
option.
Any manually created tokens to be added to the returned tokens object.
{}
Any token provided with a matching key will replace any generated by createColorProgressionTokens
.
A tokens object
// This config for a blue progression
const progressionConfig = {
baseColor: '#1157f7',
baseColorIndex: 3,
levelsCount: 7,
lightdark: (levels, index, { baseColorIndex: bi }) => {
const adjustment = (0.85 / levels.length) * 100
return index < bi ? adjustment * (bi - index) : adjustment * (index - bi)
},
}
// export name serves as the root key
export const color = {
progression: createColorProgressionTokens(progressionConfig)
}
// returns
{
'color': {
'progression': {
'100': {'value': '#c6d6fc'},
'200': {'value': '#8bacf9'},
'300': {'value': '#5083f6'},
'400': {'value': '#1157f7'},
'500': {'value': '#0a41c0'},
'600': {'value': '#072d85'},
'700': {'value': '#04194a'}
}
}
}
// This config for a red progression
const redConfig = {
baseColor: '#C63454',
levelsCount: 3,
baseColorIndex: 1,
lightdark: [8, 0, 10],
saturate: 12,
}
// Style Dictionary will merge any tokens under an existing key
export const color = {
red: createColorProgressionTokens(redConfig)
}
// returns
{
'color': {
'red': {
'100': {'value': '#d44f6c'},
'200': {'value': '#c63454'},
'300': {'value': '#a02742'}
}
}
}
color
.lighten(lightness)
.darken(darkness)
.spin(spin)
.saturate(saturation)
.desaturate(greyscale ? 100 : desaturation)
npm run demo
This script will run a sample token generation for colors and typescale. It logs configurations, token objects, and SCSS output.
- Add Typescale to UI
- Load fonts from Google
- Add font-family selector
- Add font weight slider for variable fonts
- Add font weight radio group for fixed weight fonts
- Add typscale config form
- Add typescale preview showing each font size in the scale with value and example
- add histogram of typescale to illustrate its progression
- Add color progression UI
- Add an 'add' color button with color picker
- Add color pregression config form
- Add inputs for all configurable options
- Add tests
- Add unit test for
createTypeScale
- Add unit test for
createTypeScaleTokens
- Add unit test for
createColorProgressionTokens
- Add integration tests for components
- Add unit test for