├── .npmrc ├── test ├── tokens │ ├── no-tokens.yaml │ ├── simple.yaml │ ├── token-array.yaml │ ├── invalid-token-name.yaml │ ├── basic-example │ │ ├── dark-theme.yaml │ │ ├── tokens.yaml │ │ └── base-colors.yaml │ ├── theme.yaml │ ├── theme-propagation.yaml │ ├── aliases.yaml │ ├── array.yaml │ ├── angle.yaml │ ├── errors.yaml │ ├── metadata.yaml │ ├── length.yaml │ ├── expressions.yaml │ └── colors.yaml └── token.test.js ├── CHANGELOG.md ├── .serena ├── cache │ └── typescript │ │ └── document_symbols_cache_v23-06-25.pkl ├── memories │ ├── suggested_commands.md │ ├── task_completion_checklist.md │ ├── project_overview.md │ └── code_style_conventions.md └── project.yml ├── .eslintignore ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .editorconfig ├── compare-interpolation.yaml ├── .gitignore ├── scripts ├── clean.sh ├── test.sh └── build.sh ├── bin ├── templates │ ├── yaml.hbs │ ├── json.hbs │ ├── sass.hbs │ ├── data-dump.hbs │ ├── css.hbs │ ├── html-file.hbs │ └── html-colors.hbs ├── examples │ ├── basic │ │ └── tokens.yaml │ └── advanced │ │ ├── color-scales.yaml │ │ └── tokens.yaml └── package.json ├── src ├── templates │ ├── yaml.hbs │ ├── json.hbs │ ├── sass.hbs │ ├── data-dump.hbs │ ├── css.hbs │ ├── html-file.hbs │ └── html-colors.hbs ├── utils.ts ├── formats-web.ts ├── formats-generic.ts ├── terminal.ts ├── default-formatters.ts ├── formats.ts ├── errors.ts ├── chromatic-cli.ts └── formats-styleguide.ts ├── .dependabot └── config.yml ├── .gitattributes ├── tsconfig.json ├── docs ├── errors │ └── tokens-as-array.md ├── functions.md ├── guide.md ├── token-value.md └── reference.md ├── LICENSE.txt ├── .eslintrc.js ├── CONTRIBUTING.md ├── examples ├── basic │ └── tokens.yaml └── advanced │ ├── color-scales.yaml │ └── tokens.yaml ├── README.md ├── config └── rollup.config.js ├── package.json ├── CODE_OF_CONDUCT.md ├── CLAUDE.md ├── interpolation-comparison.css ├── color-scale.scss ├── color-scales.css └── color-scales.scss /.npmrc: -------------------------------------------------------------------------------- 1 | loglevel="silent" -------------------------------------------------------------------------------- /test/tokens/no-tokens.yaml: -------------------------------------------------------------------------------- 1 | red: '#f00' 2 | green: '#0F0' 3 | -------------------------------------------------------------------------------- /test/tokens/simple.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | primary__0066ce: '#0066ce' 3 | -------------------------------------------------------------------------------- /test/tokens/token-array.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | - red: '#f00' 3 | - green: '#0f0' 4 | -------------------------------------------------------------------------------- /test/tokens/invalid-token-name.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | '<>': '#ff0000' # Invalid token name 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2019-12-01 2 | 3 | ### Features 4 | 5 | - feat(scale): Added typographic scale. `scale(12pt)` will return an array of 6 | font-sizes. See 7 | -------------------------------------------------------------------------------- /.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ui-js/chromatic/HEAD/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Third party 2 | **/node_modules 3 | 4 | # Build products 5 | build/ 6 | coverage/ 7 | dist/ 8 | stage/ 9 | bin/ 10 | 11 | # Config files 12 | config/ 13 | *.config.js -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["cosmiconfig", "luma"], 3 | "editor.formatOnSave": true, 4 | "liveServer.settings.port": 5501, 5 | "scss.format.enable": false 6 | } 7 | -------------------------------------------------------------------------------- /test/tokens/basic-example/dark-theme.yaml: -------------------------------------------------------------------------------- 1 | theme: 'dark' 2 | tokens: 3 | semantic: 4 | error: 'lighten(saturate({semantic.error}, 20%), 8%)' 5 | page-background: 'gray(15%)' 6 | text-color: 'gray(95%)' 7 | -------------------------------------------------------------------------------- /test/tokens/theme.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | background: 3 | value: 4 | dark: '#fff' 5 | light: '#333' 6 | body: 7 | value: 8 | dark: '#222' 9 | light: '#ddd' 10 | -------------------------------------------------------------------------------- /test/tokens/theme-propagation.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | single: '#00F' 3 | base: 4 | value: 5 | _: '#f00' 6 | green: '#0f0' 7 | first-level: '{base}' 8 | second-level: 'darken({first-level})' 9 | first-level-single: '{single}' 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build", 7 | "group": "build", 8 | "problemMatcher": [], 9 | "label": "npm: build", 10 | "detail": "bash ./scripts/build.sh dev" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /compare-interpolation.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | # Test yellow scale to verify hue positioning fix 3 | yellow: 'scale(hsl(60deg, 100%, 50%))' 4 | 5 | # Original RGB-based scale function 6 | red-rgb: 'scale(hsl(4deg, 90%, 50%))' 7 | blue-rgb: 'scale(hsl(210deg, 90%, 50%))' 8 | green-rgb: 'scale(hsl(130deg, 70%, 43%))' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | stage/ 4 | coverage/ 5 | .rpt2_cache/ 6 | 7 | # Secret files 8 | *secret.js 9 | 10 | # Log files 11 | npm-debug.log 12 | 13 | # Dependency directory 14 | node_modules 15 | 16 | # OS generated files 17 | .DS_Store 18 | ._* 19 | .Spotlight-V100 20 | .Trashes 21 | ehthumbs.db 22 | Thumbs.db 23 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # exit immediately on error 4 | set -o nounset # abort on unbound variable 5 | set -o pipefail # don't hide errors within pipes 6 | # set -x # for debuging, trace what is being executed. 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | rm -rf "./bin" 11 | rm -rf "./build" 12 | rm -rf "./coverage" 13 | -------------------------------------------------------------------------------- /bin/templates/yaml.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader '# '}} 2 | 3 | {{#each groups}} 4 | {{#if groupId }} 5 | 6 | # 7 | # {{ groupId }} 8 | # 9 | {{/if}} 10 | {{comment groupInfo.comment '# '}} 11 | {{#each tokens}} 12 | {{#if tokenDefinition.comment}} 13 | {{comment tokenDefinition.comment '# '}} 14 | {{/if}} 15 | {{#each themes }} 16 | {{tokenName}}: "{{{cssValue tokenValue}}}" 17 | {{/each}} 18 | {{/each}} 19 | {{/each}} 20 | -------------------------------------------------------------------------------- /src/templates/yaml.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader '# '}} 2 | 3 | {{#each groups}} 4 | {{#if groupId }} 5 | 6 | # 7 | # {{ groupId }} 8 | # 9 | {{/if}} 10 | {{comment groupInfo.comment '# '}} 11 | {{#each tokens}} 12 | {{#if tokenDefinition.comment}} 13 | {{comment tokenDefinition.comment '# '}} 14 | {{/if}} 15 | {{#each themes }} 16 | {{tokenName}}: "{{{cssValue tokenValue}}}" 17 | {{/each}} 18 | {{/each}} 19 | {{/each}} 20 | -------------------------------------------------------------------------------- /bin/templates/json.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader}} 2 | 3 | { 4 | {{#remove-last-comma}} 5 | {{#each groups}} 6 | {{#if groupId }} 7 | 8 | /* 9 | * {{ groupId }} 10 | */ 11 | 12 | {{/if}} 13 | {{comment groupInfo.comment '// '}} 14 | {{#each tokens}} 15 | {{#if tokenDefinition.comment}} 16 | {{comment tokenDefinition.comment '// '}} 17 | {{/if}} 18 | {{#each themes }} 19 | "{{tokenName}}": "{{{cssValue tokenValue}}}", 20 | {{/each}} 21 | {{/each}} 22 | {{/each}} 23 | {{/remove-last-comma}} 24 | } -------------------------------------------------------------------------------- /src/templates/json.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader}} 2 | 3 | { 4 | {{#remove-last-comma}} 5 | {{#each groups}} 6 | {{#if groupId }} 7 | 8 | /* 9 | * {{ groupId }} 10 | */ 11 | 12 | {{/if}} 13 | {{comment groupInfo.comment '// '}} 14 | {{#each tokens}} 15 | {{#if tokenDefinition.comment}} 16 | {{comment tokenDefinition.comment '// '}} 17 | {{/if}} 18 | {{#each themes }} 19 | "{{tokenName}}": "{{{cssValue tokenValue}}}", 20 | {{/each}} 21 | {{/each}} 22 | {{/each}} 23 | {{/remove-last-comma}} 24 | } -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | update_configs: 4 | # Keep package.json (& lockfiles) up to date 5 | # batching pull requests weekly 6 | - package_manager: 'javascript' 7 | directory: '/' 8 | update_schedule: 'weekly' 9 | automerged_updates: 10 | - match: 11 | # Supported dependency types: 12 | # - "development" 13 | # - "production" 14 | # - "all" 15 | dependency_type: 'all' 16 | update_type: 'semver:minor' 17 | version_requirement_updates: 'increase_versions' 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Enforce Unix newlines 5 | *.css text eol=lf 6 | *.html text eol=lf 7 | *.mjs text eol=lf 8 | *.js text eol=lf 9 | *.ts text eol=lf 10 | *.json text eol=lf 11 | *.md text eol=lf 12 | *.mjs text eol=lf 13 | *.rb text eol=lf 14 | *.scss text eol=lf 15 | *.svg text eol=lf 16 | *.txt text eol=lf 17 | *.xml text eol=lf 18 | *.yml text eol=lf 19 | *.yaml text eol=lf 20 | 21 | 22 | # Don't diff or textually merge source maps 23 | *.map binary 24 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # exit immediately on error 4 | set -o nounset # abort on unbound variable 5 | set -o pipefail # don't hide errors within pipes 6 | # set -x # for debuging, trace what is being executed. 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # Read the first argument, set it to "dev" if not set 11 | VARIANT="${1-coverage}" 12 | 13 | export TEST="true" 14 | 15 | if [ "$VARIANT" = "coverage" ]; then 16 | npx jest --coverage 17 | elif [ "$VARIANT" = "snapshot" ]; then 18 | npx jest -u 19 | else 20 | npx jest 21 | fi -------------------------------------------------------------------------------- /test/tokens/aliases.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | rouge: 'red' 3 | simple-alias__red: '{rouge}' 4 | two-level-alias__red: '{simple-alias__red}' 5 | alias-in-expression__e60000: 'darken({rouge})' 6 | alias-in-string__3px_f00: '3px {rouge}' 7 | invalid: 8 | circular-alias: 'darken({invalid.circular-alias})' 9 | two-level-circular-alias: '{invalid.one-level-circular-alias}' 10 | one-level-circular-alias: '{invalid.two-level-circular-alias}' 11 | mispelled-alias: 'darken({rogue})' 12 | alias-syntax-error: 'darken({rouge}}' # has a closing "}" instead of the expected ")" 13 | -------------------------------------------------------------------------------- /test/tokens/basic-example/tokens.yaml: -------------------------------------------------------------------------------- 1 | import: 2 | - ./base-colors.yaml 3 | - ./dark-theme.yaml 4 | tokens: 5 | scrim: 'gray(30%, 40%)' 6 | cta-button-background: '{primary}' 7 | page-background: 'gray(98%)' 8 | text-color: 'gray(5%)' 9 | cta-button-color: 'contrast({primary}, #ddd, #333)' 10 | link-color: '{blue}' 11 | link-active-color: 'saturate(darken({link-color}, 5%), 15%)' 12 | link-down-color: 'darken({link-color}, 10%)' 13 | link-hover-color: 'saturate(darken({link-color}, 5%), 10%)' 14 | warm-grey: mix(gray(50%), red, 6%) 15 | cool-grey: mix(gray(50%), green, 6%) 16 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # exit immediately on error 4 | set -o nounset # abort on unbound variable 5 | set -o pipefail # don't hide errors within pipes 6 | # set -x # for debuging, trace what is being executed. 7 | 8 | cd "$(dirname "$0")/.." 9 | 10 | # Read the first argument, set it to "dev" if not set 11 | export BUILD="${1-dev}" 12 | 13 | # If no "node_modules" directory, do an install first 14 | if [ ! -d "./node_modules" ]; then 15 | echo -e "\033[40m`basename "$0"`\033[0m 🚀 Installing dependencies" 16 | npm install 17 | fi 18 | 19 | rm -rf ./bin 20 | npx rollup --config ./config/rollup.config.js 21 | -------------------------------------------------------------------------------- /test/tokens/array.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | primary: '#0066ce' 3 | auto-scaling__80b3e7: '{primary-200}' 4 | array: '[blue, white, red]' 5 | array-literal__f0f: '[yellow, magenta, cyan][1]' 6 | array-index__00f: '{array}[0]' 7 | array-index-expression_fff: '{array}[ 24 / 12 - 1 ]' 8 | manual-scaling__80b3e7: 'scale({primary})[2]' 9 | invalid: 10 | unterminated-array-index: '{array}[1+2' 11 | array-index-multiple-arguments: '{array}[2, 4 ]' 12 | array-index-length: '{array}[2px]' 13 | array-index-string: '{array}["0"]' 14 | index-of-float: '12[2]' 15 | index-of-string: '"hello"[2]' 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // "allowJs": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "incremental": true, 9 | "module": "ESNext", 10 | "moduleResolution": "node", 11 | // "newLine": "lf", 12 | "noImplicitAny": false, 13 | "noLib": false, 14 | "removeComments": true, 15 | "sourceMap": true, 16 | // "strictNullChecks": true, 17 | "target": "es2019", 18 | 19 | "lib": ["es2017", "dom", "dom.iterable", "scripthost"] 20 | }, 21 | "exclude": ["node_modules", "**/*.spec.ts", "bin"] 22 | } 23 | -------------------------------------------------------------------------------- /test/tokens/angle.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | canonical: '45deg' 3 | rad__55deg: '0.785398rad + 10deg' 4 | grad__55deg: '50grad + 10deg' 5 | turn__100deg: '(1/4)turn + 10deg' 6 | add__45deg: '40deg + 5deg' 7 | substract__45deg: '90deg - 50grad' 8 | divide-by-number__45deg: '90deg / 2' 9 | divide__2: '90deg / 45deg' 10 | multiply-by-number-right__15deg: '3deg * 5' 11 | multiply-by-number-left__15deg: '5 * 3deg' 12 | invalid: 13 | number-plus-angle: '42 + 10deg' 14 | add-color: '3deg + #fff' 15 | multiply-deg: '3deg * 5rad' 16 | percent-plus-angle: '10% + 30deg' 17 | angle-plus-number: '10deg + 5' 18 | angle-plus-percent: '10deg + 10%' 19 | angle-plus-color: '10deg + #fa7' 20 | angle-plus-dimen: '10deg + 10px' 21 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | const stringSimilarity = require('string-similarity'); 2 | 3 | export function findClosestKey( 4 | key: string, 5 | o: Record | Map | string[] 6 | ): string { 7 | if (!key || !o) return ''; 8 | let keys: string[]; 9 | if (o instanceof Map) { 10 | keys = Array.from(o.keys()); 11 | } else if (Array.isArray(o)) { 12 | keys = o; 13 | } else { 14 | keys = Object.keys(o); 15 | } 16 | if (keys.length === 0) return ''; 17 | const result = stringSimilarity.findBestMatch(key, keys); 18 | return result.bestMatch.rating > 0.1 ? result.bestMatch.target : ''; 19 | } 20 | 21 | export function getSuggestion( 22 | key: string, 23 | o: Record | Map | string[] 24 | ): string { 25 | const alt = findClosestKey(key, o); 26 | return alt ? `. Did you mean "${alt}"?` : ''; 27 | } 28 | -------------------------------------------------------------------------------- /docs/errors/tokens-as-array.md: -------------------------------------------------------------------------------- 1 | # Error Message 2 | 3 | ```shell 4 | The "tokens" property is an array. It should be a key/value map of tokens. 5 | ``` 6 | 7 | While parsing a token file, a `"tokens"` property was encountered which was 8 | encoded as an array, denoted by a "-" in front of the element names in YAML. 9 | 10 | ```yaml 11 | # ❌ 12 | tokens: 13 | - red: '#f00' 14 | - green: '#0f0' 15 | ``` 16 | 17 | Instead, each token should be a key/value pair: 18 | 19 | ```yaml 20 | # ✔︎ 21 | tokens: 22 | red: '#f00' 23 | green: '#0f0' 24 | ``` 25 | 26 | This error can also occur if a children of a token includes an array instead of 27 | key/value pair: 28 | 29 | ```yaml 30 | # ❌ 31 | tokens: 32 | red: 33 | - dark: '#c00' 34 | - light: '#d00' 35 | ``` 36 | 37 | Instead: 38 | 39 | ```yaml 40 | # ✔︎ 41 | tokens: 42 | red: 43 | dark: '#c00' 44 | light: '#d00' 45 | ``` 46 | -------------------------------------------------------------------------------- /test/tokens/errors.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | array: '[3, 5, 7, 13]' 3 | circular-token: '{circular-token}' 4 | inconsistent-token: 5 | value: 6 | dark: '#ff0' 7 | light: 2px 8 | expect-color: 'contrast(12px)' 9 | unknown-unit: '12km' 10 | missing-argument: 'min(12)' 11 | unexpected-argument: 'min(#ff0, #f0f)' 12 | too-many-arguments: 'min(3, 4, 5)' 13 | unexpected-open-bracket: '12px[0]' 14 | unclosed-array-index: '{array}[0' 15 | unclosed-array-literal: '[3, 5, 7' 16 | array-syntax-error: '[3;5]' 17 | string-index: '{array}["0"]' 18 | unclosed-string: '"hello' 19 | unknown-function: 'foo(12)' 20 | arg-list-syntax-error: 'min(12;14)' 21 | expected-operand-term: '12+' 22 | expected-operand-prod: '12*' 23 | invalid-operand-prod: '(#fff/2)+15px' 24 | invalid-unary-operand: '-#fff' 25 | invalid-operand-term: '10%+"hello"' 26 | unterminated-group: '(1+5' 27 | -------------------------------------------------------------------------------- /test/tokens/metadata.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | primary: 3 | comment: 'Use sparingly' 4 | remarks: > 5 | Here's to the crazy ones. 6 | The misfits. 7 | The rebels. 8 | The troublemakers. 9 | The **round pegs** in the _square holes_. 10 | 11 | The ones who see things differently. 12 | 13 | They're not fond of rules. 14 | And they have no respect for the status quo. 15 | 16 | You can quote them, disagree with them, 17 | glorify or vilify them. 18 | About the only thing you can't do is ignore them. 19 | 20 | Because they change things. 21 | 22 | They push the human race forward. 23 | 24 | While some may see them as the crazy ones, 25 | we see genius. 26 | 27 | Because the people who are crazy enough to think 28 | they can change the world, are the ones who do. 29 | 30 | value: 'hsl(230, 100%, 50%)' 31 | -------------------------------------------------------------------------------- /src/formats-web.ts: -------------------------------------------------------------------------------- 1 | // @todo more formats (https://github.com/amzn/style-dictionary/blob/3d0d1c0356d42fc83f905a7e7e4b1c662c77de0b/lib/common/formats.js) 2 | 3 | // xCode .colorset: https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/Named_Color.html#//apple_ref/doc/uid/TP40015170-CH59-SW1 4 | 5 | /* 6 | - sketch/palette 7 | - sketch/palette/v2 8 | */ 9 | 10 | const fs = require('fs'); 11 | import { Format, RenderContext } from './formats'; 12 | 13 | export const WebFormats: { formats: { [key: string]: Format } } = { 14 | formats: { 15 | sass: { 16 | ext: '.scss', 17 | render: (context: RenderContext): string => 18 | context.renderTemplate( 19 | fs.readFileSync(__dirname + '/templates/sass.hbs', 'utf-8'), 20 | context 21 | ), 22 | }, 23 | css: { 24 | ext: '.css', 25 | render: (context: RenderContext): string => 26 | context.renderTemplate( 27 | fs.readFileSync(__dirname + '/templates/css.hbs', 'utf-8'), 28 | context 29 | ), 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 - present Arno Gourdol. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/formats-generic.ts: -------------------------------------------------------------------------------- 1 | /* 2 | - json/asset 3 | - json/nested 4 | - json/flat 5 | - sketch/palette 6 | - sketch/palette/v2 7 | */ 8 | const fs = require('fs'); 9 | 10 | import { Format, RenderContext } from './formats'; 11 | 12 | export const GenericFormats: { formats: { [key: string]: Format } } = { 13 | formats: { 14 | 'yaml': { 15 | ext: '.yaml', 16 | 17 | render: (context: RenderContext): string => 18 | context.renderTemplate( 19 | fs.readFileSync(__dirname + '/templates/yaml.hbs', 'utf-8'), 20 | context 21 | ), 22 | }, 23 | 24 | 'json': { 25 | ext: '.json', 26 | 27 | render: (context: RenderContext): string => 28 | context.renderTemplate( 29 | fs.readFileSync(__dirname + '/templates/json.hbs', 'utf-8'), 30 | context 31 | ), 32 | 33 | handlebarsHelpers: {}, 34 | }, 35 | 'data-dump': { 36 | ext: '.yaml', 37 | 38 | render: (context: RenderContext): string => 39 | context.renderTemplate( 40 | fs.readFileSync(__dirname + '/templates/data-dump.hbs', 'utf-8'), 41 | context 42 | ), 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /test/tokens/basic-example/base-colors.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | red: 'hsl(2.2,67.9%,52.4%) ' 3 | orange: 'hsl(38.8,100%,50%)' 4 | yellow: 'hsl(48,100%,50%)' 5 | green: 'hsl(85.1,41.1%,35.3%)' 6 | blue: 'hsl(208.1,69.6%,45.1%)' 7 | purple: 'hsl(279.9,68%,59.6%)' 8 | 9 | primary-hue: '210deg' 10 | primary: 'hsl({primary-hue}, 90%, 40%)' 11 | 12 | volcano: '#FFEB3B' 13 | volcano-100: 'scale({volcano})[1]' 14 | volcano-200: 'scale({volcano})[2]' 15 | volcano-300: 'scale({volcano})[3]' 16 | volcano-400: 'scale({volcano})[4]' 17 | volcano-500: 'scale({volcano})[5]' 18 | volcano-600: 'scale({volcano})[6]' 19 | volcano-700: 'scale({volcano})[7]' 20 | volcano-800: 'scale({volcano})[8]' 21 | volcano-900: 'scale({volcano})[9]' 22 | 23 | semantic: 24 | success: 25 | comment: 'Indicate a completed task.' 26 | value: '{green}' 27 | warning: 28 | comment: 'Indicate a problem that does not prevent a task from being completed.' 29 | value: '{orange}' 30 | error: 31 | comment: 'Indicate an error that prevents a task from being completed.' 32 | value: '{red}' 33 | -------------------------------------------------------------------------------- /bin/templates/sass.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader}} 2 | {{#each themes}} 3 | {{#if theme}} 4 | 5 | {{! Output Sass properties pointing to the CSS custom property }} 6 | {{#if isDefaultTheme}} 7 | {{#each tokens}} 8 | ${{sanitizeCssPropertyName tokenName}}: var(--{{sanitizeCssPropertyName 9 | tokenName 10 | }}) !default; 11 | {{/each}} 12 | {{/if}} 13 | 14 | {{! This is a themed tokens (multiple definitions }} 15 | {{#if isDefaultTheme}} 16 | {{! The current theme is the default theme ('_') }} 17 | :root { 18 | {{else}} 19 | {{! The current theme is a custom }} 20 | body[data-theme="{{theme}}"] { 21 | {{/if}} 22 | {{#each tokens}} 23 | {{! Output a custom property }} 24 | --{{sanitizeCssPropertyName tokenName}}: {{{cssValue tokenValue}}}; 25 | {{/each}} 26 | }; 27 | 28 | {{else}} 29 | 30 | {{! Output all tokens that have a single definition (no theme) as a Sass variable}} 31 | {{#each tokens}} 32 | {{#if tokenDefinition.comment}} 33 | {{comment tokenDefinition.comment '//'}} 34 | {{/if}} 35 | ${{sanitizeCssPropertyName tokenName}}: {{{cssValue tokenValue}}} !default; 36 | {{/each}} 37 | 38 | 39 | {{/if}} 40 | {{/each}} 41 | 42 | {{#each colorRamps}} 43 | {{this}} 44 | ${{sanitizeCssPropertyName name}}: {{{cssValue value}}} 45 | {{/each}} 46 | -------------------------------------------------------------------------------- /src/templates/sass.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader}} 2 | {{#each themes}} 3 | {{#if theme}} 4 | 5 | {{! Output Sass properties pointing to the CSS custom property }} 6 | {{#if isDefaultTheme}} 7 | {{#each tokens}} 8 | ${{sanitizeCssPropertyName tokenName}}: var(--{{sanitizeCssPropertyName 9 | tokenName 10 | }}) !default; 11 | {{/each}} 12 | {{/if}} 13 | 14 | {{! This is a themed tokens (multiple definitions }} 15 | {{#if isDefaultTheme}} 16 | {{! The current theme is the default theme ('_') }} 17 | :root { 18 | {{else}} 19 | {{! The current theme is a custom }} 20 | body[data-theme="{{theme}}"] { 21 | {{/if}} 22 | {{#each tokens}} 23 | {{! Output a custom property }} 24 | --{{sanitizeCssPropertyName tokenName}}: {{{cssValue tokenValue}}}; 25 | {{/each}} 26 | }; 27 | 28 | {{else}} 29 | 30 | {{! Output all tokens that have a single definition (no theme) as a Sass variable}} 31 | {{#each tokens}} 32 | {{#if tokenDefinition.comment}} 33 | {{comment tokenDefinition.comment '//'}} 34 | {{/if}} 35 | ${{sanitizeCssPropertyName tokenName}}: {{{cssValue tokenValue}}} !default; 36 | {{/each}} 37 | 38 | 39 | {{/if}} 40 | {{/each}} 41 | 42 | {{#each colorRamps}} 43 | {{this}} 44 | ${{sanitizeCssPropertyName name}}: {{{cssValue value}}} 45 | {{/each}} 46 | -------------------------------------------------------------------------------- /.serena/memories/suggested_commands.md: -------------------------------------------------------------------------------- 1 | # Development Commands 2 | 3 | ## Building 4 | - `npm run build` - Build development version 5 | - `npm run build:prod` - Build production version with minification 6 | - `npm run watch` - Watch mode for development (rollup --watch) 7 | - `npm run clean` - Clean build artifacts 8 | 9 | ## Testing 10 | - `npm test` - Run all tests 11 | - `npm run coverage` - Run tests with coverage report 12 | - `npm run snapshot` - Update test snapshots 13 | 14 | ## Code Quality 15 | - `npm run lint` - Format code with Prettier (auto-fixes) 16 | - Formats: `**/*.{ts,js,css,md,yml,json}` 17 | - Excludes: vendor directory 18 | - ESLint is run during production builds 19 | 20 | ## Running Chromatic 21 | - `npm run chromatic` - Run the local chromatic CLI 22 | - `./bin/chromatic` - Direct execution after build 23 | 24 | ## Git Hooks 25 | - Pre-commit hook via Husky + lint-staged 26 | - TypeScript files: ESLint fix 27 | - JS/CSS/JSON/MD files: Prettier formatting 28 | 29 | ## System Commands (macOS/Darwin) 30 | - Standard Unix commands: `git`, `ls`, `cd`, `grep`, `find` 31 | - Note: macOS may have BSD versions of some utilities 32 | 33 | ## Example Commands 34 | ```bash 35 | # Create example directory 36 | chromatic example ./test 37 | 38 | # Generate output files 39 | chromatic ./test -o tokens.scss 40 | chromatic ./test -o tokens.html 41 | chromatic tokens.yaml -o tokens.scss --format scss 42 | ``` -------------------------------------------------------------------------------- /bin/templates/data-dump.hbs: -------------------------------------------------------------------------------- 1 | filepath: "{{filepath}}" 2 | fileHeader: "{{fileHeader}}" 3 | themes: 4 | {{#each themes}} 5 | - theme: "{{theme}}": 6 | isDefaultTheme: {{isDefaultTheme}} 7 | tokens: 8 | {{#each tokens}} 9 | - tokenId: "{{tokenId}}" 10 | tokenName: "{{tokenName}}" 11 | tokenDefinition: 12 | comment: {{tokenDefinition.comment}} 13 | remarks: {{tokenDefinition.remarks}} 14 | deprecated: {{tokenDefinition.deprecated}} 15 | tokenValue: "{{cssValue tokenValue}}" 16 | {{/each}} 17 | {{/each}} 18 | groups: 19 | {{#each groups}} 20 | - groupId: "{{groupId}}" 21 | groupInfo: 22 | name: "{{groupInfo.name}}" 23 | comment: "{{groupInfo.comment}}" 24 | remarks: "{{groupInfo.remarks}}" 25 | tokens: 26 | {{#each tokens}} 27 | - tokenId: "{{tokenId}}" 28 | tokenDefinition: 29 | comment: {{tokenDefinition.comment}} 30 | remarks: {{tokenDefinition.remarks}} 31 | deprecated: {{tokenDefinition.deprecated}} 32 | themes: 33 | {{#each themes}} 34 | - theme: "{{theme}}" 35 | tokenName: "{{tokenName}}" 36 | tokenValue: "{{cssValue tokenValue}}" 37 | {{/each}} 38 | {{/each}} 39 | {{/each}} 40 | -------------------------------------------------------------------------------- /src/templates/data-dump.hbs: -------------------------------------------------------------------------------- 1 | filepath: "{{filepath}}" 2 | fileHeader: "{{fileHeader}}" 3 | themes: 4 | {{#each themes}} 5 | - theme: "{{theme}}": 6 | isDefaultTheme: {{isDefaultTheme}} 7 | tokens: 8 | {{#each tokens}} 9 | - tokenId: "{{tokenId}}" 10 | tokenName: "{{tokenName}}" 11 | tokenDefinition: 12 | comment: {{tokenDefinition.comment}} 13 | remarks: {{tokenDefinition.remarks}} 14 | deprecated: {{tokenDefinition.deprecated}} 15 | tokenValue: "{{cssValue tokenValue}}" 16 | {{/each}} 17 | {{/each}} 18 | groups: 19 | {{#each groups}} 20 | - groupId: "{{groupId}}" 21 | groupInfo: 22 | name: "{{groupInfo.name}}" 23 | comment: "{{groupInfo.comment}}" 24 | remarks: "{{groupInfo.remarks}}" 25 | tokens: 26 | {{#each tokens}} 27 | - tokenId: "{{tokenId}}" 28 | tokenDefinition: 29 | comment: {{tokenDefinition.comment}} 30 | remarks: {{tokenDefinition.remarks}} 31 | deprecated: {{tokenDefinition.deprecated}} 32 | themes: 33 | {{#each themes}} 34 | - theme: "{{theme}}" 35 | tokenName: "{{tokenName}}" 36 | tokenValue: "{{cssValue tokenValue}}" 37 | {{/each}} 38 | {{/each}} 39 | {{/each}} 40 | -------------------------------------------------------------------------------- /.serena/memories/task_completion_checklist.md: -------------------------------------------------------------------------------- 1 | # Task Completion Checklist 2 | 3 | When completing any development task in the Chromatic codebase, ensure: 4 | 5 | ## Before Committing 6 | 1. **Format Code**: Run `npm run lint` to format all code with Prettier 7 | 2. **Test Changes**: Run `npm test` to ensure all tests pass 8 | 3. **Update Snapshots**: If test output changed intentionally, run `npm run snapshot` 9 | 4. **Build Check**: Run `npm run build` to ensure TypeScript compiles without errors 10 | 11 | ## Code Quality Checks 12 | - TypeScript compiles without errors 13 | - No ESLint warnings in production build 14 | - Tests pass (including any new tests added) 15 | - Code follows existing patterns and conventions 16 | - No console.log statements left in production code 17 | 18 | ## For Feature Development 19 | - Add appropriate tests in `/test` directory 20 | - Update examples if applicable 21 | - Consider theme support for new features 22 | - Ensure cross-platform compatibility (web/iOS/Android) 23 | 24 | ## For Bug Fixes 25 | - Add regression test if possible 26 | - Verify fix doesn't break existing functionality 27 | - Check all output formats still work correctly 28 | 29 | ## Documentation 30 | - Update inline comments for complex logic 31 | - Update README.md if user-facing changes 32 | - Update CHANGELOG.md for notable changes 33 | 34 | ## Git Workflow 35 | - Husky pre-commit hooks will auto-format staged files 36 | - Ensure commit messages are descriptive 37 | - Reference issue numbers when applicable -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = { 3 | root: true, 4 | // Use the Typescript parser: 5 | parser: '@typescript-eslint/parser', 6 | extends: [ 7 | // Uses the recommended rules for Typescript 8 | 'plugin:@typescript-eslint/recommended', 9 | // Disable rules that conflict with prettier 10 | // See https://prettier.io/docs/en/integrating-with-linters.html 11 | 'plugin:prettier/recommended', 12 | ], 13 | parserOptions: { 14 | project: './tsconfig.json', 15 | // Configure the parser with the tsconfig file in the root project 16 | // (not the one in the local workspace) 17 | // tsconfigRootDir: path.resolve(__dirname, './src/'), 18 | // Allows for the parsing of modern ECMAScript features 19 | ecmaVersion: 2018, 20 | // Allows for the use of module imports 21 | sourceType: 'module', 22 | // ecmaFeatures: { 23 | // jsx: true, // Allows for the parsing of JSX 24 | // }, 25 | }, 26 | env: { 27 | es6: true, 28 | node: true, 29 | }, 30 | rules: { 31 | '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 32 | '@typescript-eslint/no-explicit-any': ['off'], 33 | '@typescript-eslint/no-var-requires': ['off'], 34 | '@typescript-eslint/no-use-before-define': ['off'], 35 | 36 | 'indent': 'off', 37 | 'no-use-before-define': [ 38 | 'off', 39 | { 40 | functions: false, 41 | classes: false, 42 | }, 43 | ], 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Project 2 | 3 | There are many ways you can get involved with the project. Contributing to an 4 | open source project is fun and rewarding. 5 | 6 | ## Funding 7 | 8 | Consider donating to project development via 9 | [Patreon](https://patreon.com/arnog) (recurring donation) or 10 | [PayPal](https://www.paypal.me/arnogourdol) (one time donation). 11 | 12 | Encourage the business partners in your organization to provide financial 13 | support of open source projects. 14 | 15 | Funds go to general development, support, and infrastructure costs. 16 | 17 | We welcome both individual and corporate sponsors. In addition to Patreon and 18 | PayPal, we can also accept short-term development contracts for specific 19 | features or maintenance of the project. 20 | 21 | ## Contributing Issues 22 | 23 | If you're running into some problems or something doesn't behave the way you 24 | think it should, please file an issue in GitHub. 25 | 26 | Before filing something, have a look at the existing issues. It's better to 27 | avoid filing duplicates. You can add a comment to an existing issue if you'd 28 | like. 29 | 30 | ### Can I help fix a bug? 31 | 32 | Sure! Have a look at the issue report, and make sure no one is already working 33 | on it. If the issue is assigned to someone, they're on it! Otherwise, add a 34 | comment in the issue indicating you'd like to work on resolving the issue and go 35 | for it! 36 | 37 | Whether you have a fix for an issue, some improved test cases, or a brand new 38 | feature, we welcome contributions in the form of pull requests. 39 | -------------------------------------------------------------------------------- /bin/templates/css.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader}} 2 | {{#each themes}} 3 | {{#if theme}} 4 | 5 | {{! Output css classes corresponding to the property... }} 6 | {{#if isDefaultTheme}} 7 | {{#each tokens}} 8 | .{{sanitizeCssPropertyName tokenName}} 9 | { color: 10 | {{cssValue tokenValue}}; } 11 | {{/each}} 12 | {{/if}} 13 | 14 | {{! This is a themed tokens (multiple definitions }} 15 | {{#if isDefaultTheme}} 16 | {{! The current theme is the default theme ('_') }} 17 | :root { 18 | {{else}} 19 | {{! The current theme is a custom }} 20 | body[data-theme="{{theme}}"] { 21 | {{/if}} 22 | 23 | {{#each tokens}} 24 | {{! Output a custom property }} 25 | {{#if isColor}} 26 | --{{sanitizeCssPropertyName tokenId}}: {{{cssValue tokenValue}}}; 27 | {{/if}} 28 | {{/each}} 29 | 30 | }; 31 | 32 | {{else}} 33 | 34 | {{! Output all tokens that have a single definition (no theme) as a CSS variable}} 35 | 36 | :root { 37 | {{#each tokens}} 38 | {{! Output a custom property }} 39 | {{#if isColor}} 40 | --{{sanitizeCssPropertyName tokenId}}: {{{cssValue tokenValue}}}; 41 | {{/if}} 42 | {{/each}} 43 | } 44 | 45 | 46 | {{#each tokens}} 47 | {{#if isColor}} 48 | {{#if tokenDefinition.comment}} 49 | {{comment tokenDefinition.comment '//'}} 50 | {{/if}} 51 | .{{sanitizeCssPropertyName tokenName}} 52 | { color: var(--{{sanitizeCssPropertyName tokenName}}); } 53 | {{/if}} 54 | {{/each}} 55 | 56 | {{/if}} 57 | {{/each}} -------------------------------------------------------------------------------- /src/templates/css.hbs: -------------------------------------------------------------------------------- 1 | {{comment fileHeader}} 2 | {{#each themes}} 3 | {{#if theme}} 4 | 5 | {{! Output css classes corresponding to the property... }} 6 | {{#if isDefaultTheme}} 7 | {{#each tokens}} 8 | .{{sanitizeCssPropertyName tokenName}} 9 | { color: 10 | {{cssValue tokenValue}}; } 11 | {{/each}} 12 | {{/if}} 13 | 14 | {{! This is a themed tokens (multiple definitions }} 15 | {{#if isDefaultTheme}} 16 | {{! The current theme is the default theme ('_') }} 17 | :root { 18 | {{else}} 19 | {{! The current theme is a custom }} 20 | body[data-theme="{{theme}}"] { 21 | {{/if}} 22 | 23 | {{#each tokens}} 24 | {{! Output a custom property }} 25 | {{#if isColor}} 26 | --{{sanitizeCssPropertyName tokenId}}: {{{cssValue tokenValue}}}; 27 | {{/if}} 28 | {{/each}} 29 | 30 | }; 31 | 32 | {{else}} 33 | 34 | {{! Output all tokens that have a single definition (no theme) as a CSS variable}} 35 | 36 | :root { 37 | {{#each tokens}} 38 | {{! Output a custom property }} 39 | {{#if isColor}} 40 | --{{sanitizeCssPropertyName tokenId}}: {{{cssValue tokenValue}}}; 41 | {{/if}} 42 | {{/each}} 43 | } 44 | 45 | 46 | {{#each tokens}} 47 | {{#if isColor}} 48 | {{#if tokenDefinition.comment}} 49 | {{comment tokenDefinition.comment '//'}} 50 | {{/if}} 51 | .{{sanitizeCssPropertyName tokenName}} 52 | { color: var(--{{sanitizeCssPropertyName tokenName}}); } 53 | {{/if}} 54 | {{/each}} 55 | 56 | {{/if}} 57 | {{/each}} -------------------------------------------------------------------------------- /test/token.test.js: -------------------------------------------------------------------------------- 1 | const chromatic = require('../bin/chromatic.js'); 2 | 3 | function c(s, options = {}) { 4 | return chromatic('./test/tokens/' + s + '.yaml', { 5 | header: '', 6 | console: 'log', 7 | ignoreErrors: true, 8 | ...options, 9 | }); 10 | } 11 | 12 | const testFiles = { 13 | 'simple': 'evaluates a simple token file', 14 | 'no-tokens': 'evaluates a file with no tokens', 15 | 'token-array': 'evaluates a file with an array of tokens', 16 | 'invalid-token-name': 'evaluates a token file with an invalid token name', 17 | 'expressions': 'evaluates expressions in token values correctly', 18 | 'angle': 'evaluates angles correctly', 19 | 'colors': 'evaluates color tokens correctly', 20 | 'aliases': 'evaluates aliases in token values correctly', 21 | 'metadata': 'evaluates comments, etc... associated with a token', 22 | 'theme': 'evaluates two themes', 23 | 'array': 'evaluates arrays', 24 | 'length': 'evaluates lengths', 25 | 'theme-propagation': 'propagate theme values', 26 | 'errors': 'handles syntax errors', 27 | }; 28 | 29 | Object.keys(testFiles).forEach((x) => { 30 | it(testFiles[x], () => { 31 | expect(c(x)).toMatchSnapshot(); 32 | }); 33 | }); 34 | 35 | it('generates a style guide', () => { 36 | expect( 37 | c('../../examples/advanced/tokens', { format: 'html' }) 38 | ).toMatchSnapshot(); 39 | }); 40 | 41 | it('generates a Sass stylesheet', () => { 42 | expect(c('basic-example/tokens', { format: 'sass' })).toMatchSnapshot(); 43 | }); 44 | 45 | it('generates a JSON file', () => { 46 | expect(c('basic-example/tokens', { format: 'json' })).toMatchSnapshot(); 47 | }); 48 | -------------------------------------------------------------------------------- /.serena/memories/project_overview.md: -------------------------------------------------------------------------------- 1 | # Chromatic Project Overview 2 | 3 | ## Purpose 4 | Chromatic is a build system for managing cross-platform design systems using design tokens. It generates platform-specific files from source files describing design tokens. 5 | 6 | ## Key Features 7 | - **Expressive Design Tokens**: Supports rich expressions with arithmetic operations, units, functions, and references to other tokens 8 | - **Themes Support**: Each token can have theme variants (dark/light, compact/cozy) 9 | - **Zero-conf**: Simple YAML or JSON token files are all you need to get started 10 | - **Multi-platform**: Generates artifacts for web (Sass, CSS), iOS (JSON, plist), Android (XML), and HTML style guides 11 | - **CLI and API**: Available as both a command-line tool and a programmatic API 12 | 13 | ## Tech Stack 14 | - **Language**: TypeScript (ES2019 target) 15 | - **Build Tool**: Rollup 16 | - **Testing**: Jest 17 | - **Linting**: ESLint with TypeScript plugin 18 | - **Formatting**: Prettier 19 | - **Package Manager**: npm (Node >=16.14.2) 20 | 21 | ## Main Dependencies 22 | - chalk (terminal colors) 23 | - chokidar (file watching) 24 | - chroma-js, color (color manipulation) 25 | - handlebars (templating) 26 | - yaml, json5 (config parsing) 27 | - yargs (CLI arguments) 28 | - cosmiconfig (config file discovery) 29 | 30 | ## Repository Structure 31 | - `/src` - TypeScript source code 32 | - `/bin` - Compiled JavaScript output (generated) 33 | - `/test` - Jest test files 34 | - `/scripts` - Build and test shell scripts 35 | - `/config` - Rollup configuration 36 | - `/examples` - Example token files 37 | - `/docs` - Documentation 38 | - `/src/templates` - Handlebars templates for output formats -------------------------------------------------------------------------------- /test/tokens/length.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | length-cm: '1cm + 1px' 3 | length-mm: '1mm + 1px' 4 | length-Q: '1Q + 1px' 5 | length-in: '1in + 1px' 6 | length-pc: '1pc + 1px' 7 | length-pt: '1pt + 1px' 8 | length-px: '1px + 0px' 9 | divide-two-lengths-different-relative-unit__NaN: '2em / 5rem' 10 | divide-two-lengths-same-relative-units__2: '4em / 2em' 11 | multiply-two-lengths__throw: '2em * 5rem' 12 | product-mul__15px: '3 * 5px' 13 | add-px__calc-2em-plus-15px: '2em + 3px + 5px' 14 | term-zero__36pt: '36pt - 0px' 15 | term-zero-relative-unit__2em: '2em - 0px' 16 | length-cm__38-8px: '1cm + 1px' 17 | divide-two-lengths-same-unit__2: '10em / 5em' 18 | divide-two-lengths-absolute-units__7-5: '20px / 2pt' 19 | substract-to-0__0: 2em - 2em 20 | two-em-and-3ex__calc: '2em + 3ex' 21 | two-em-and-3px__calc: '2em + 3px' 22 | add-px-alias__calc__2em-plus-8px: '{two-em-and-3px__calc} + 5px' 23 | add-em__calc-4em-plus-3px: '{two-em-and-3px__calc} + 2em' 24 | multiply-lhs-calc-4em-plus-6px: '2 * {two-em-and-3px__calc}' 25 | multiply-rhs-calc-4em-plus-6px: '{two-em-and-3px__calc} * 2' 26 | substract-to-absolute__3px: '{two-em-and-3px__calc} - 2em' 27 | dimen-plus-number: '12px + 5' 28 | dimen-plus-angle: '12px + 10deg' 29 | dimen-plus-percent: '12px + 5%' 30 | length-rem: '1rem + 0px' 31 | length-em: '1em + 0px' 32 | scale-default: 'scale(12pt)' 33 | scale-pentatonic: 'scale(1em, "pentatonic")' 34 | scale-golden: 'scale(1em, "golden")' 35 | scale-5-2: 'scale(1em, "5:8")' 36 | body: '16px' 37 | scaled-body-heading: '{body-600}' 38 | scaled-body-tiny: '{body-50}' 39 | invalid: 40 | scale-multi: 'scale(1em + 10px)' 41 | unknown-unit: '12km' 42 | dimen-plus-color: '12px + #f7a' 43 | -------------------------------------------------------------------------------- /examples/basic/tokens.yaml: -------------------------------------------------------------------------------- 1 | # This token file is using the .yaml format, but tokens files can also 2 | # be authored using JSON5 (JSON with comments) 3 | 4 | # A token file should have a mandatory "tokens" key 5 | tokens: 6 | # Token names can contain letters, digits, '_' or '-'. 7 | # The value of a token can be a literal (constant) or an expression. 8 | # Some literals have units, here 'px' for pixels. 9 | border-radius: '4px' 10 | 11 | # Here we define a length using an arithmetic expression. 12 | # The syntax of token expressions follows closely the CSS level-4 syntax. 13 | line-height: '18pt + 4px' 14 | 15 | # Defining an angle using the 'deg' unit 16 | primary-hue: '200deg' 17 | 18 | # A token expression can reference another token by enclosing it in "{}" 19 | # Functions can be used to perform more complex calculations 20 | # The 'hsl()' function will return a color based on a hue, saturation and lightness 21 | # Other color functions include "rgb()", "hsv()", "hwb()" and "lab()" 22 | primary: 'hsl({primary-hue}, 100%, 40%)' 23 | cta-button-background: '{primary}' 24 | cta-button-background-active: 'darken({cta-button-background}, 20%)' 25 | cta-button-background-hover: 'darken({cta-button-background}, 10%)' 26 | 27 | red: 'hsl(348, 86%, 61%)' 28 | orange: 'hsl(14, 100%, 53%)' 29 | green: 'hsl(141, 53%, 53%)' 30 | 31 | # Related tokens can be grouped together 32 | semantic: 33 | # Color scales (darker or lighter variant of a base color) are 34 | # created automatically and can be referenced by adding a "-" and three 35 | # digits after a token name. 36 | error: '{red-600}' 37 | warning: '{orange-400}' 38 | success: '{green-600}' 39 | 40 | groups: 41 | semantic: 42 | comment: 'These color values are used to convey a meaning' 43 | remarks: '**For more information** about the hidden meaning of semantic colors [read this](https://en.wikipedia.org/wiki/Color_symbolism)' 44 | -------------------------------------------------------------------------------- /bin/examples/basic/tokens.yaml: -------------------------------------------------------------------------------- 1 | # This token file is using the .yaml format, but tokens files can also 2 | # be authored using JSON5 (JSON with comments) 3 | 4 | # A token file should have a mandatory "tokens" key 5 | tokens: 6 | # Token names can contain letters, digits, '_' or '-'. 7 | # The value of a token can be a literal (constant) or an expression. 8 | # Some literals have units, here 'px' for pixels. 9 | border-radius: '4px' 10 | 11 | # Here we define a length using an arithmetic expression. 12 | # The syntax of token expressions follows closely the CSS level-4 syntax. 13 | line-height: '18pt + 4px' 14 | 15 | # Defining an angle using the 'deg' unit 16 | primary-hue: '200deg' 17 | 18 | # A token expression can reference another token by enclosing it in "{}" 19 | # Functions can be used to perform more complex calculations 20 | # The 'hsl()' function will return a color based on a hue, saturation and lightness 21 | # Other color functions include "rgb()", "hsv()", "hwb()" and "lab()" 22 | primary: 'hsl({primary-hue}, 100%, 40%)' 23 | cta-button-background: '{primary}' 24 | cta-button-background-active: 'darken({cta-button-background}, 20%)' 25 | cta-button-background-hover: 'darken({cta-button-background}, 10%)' 26 | 27 | red: 'hsl(348, 86%, 61%)' 28 | orange: 'hsl(14, 100%, 53%)' 29 | green: 'hsl(141, 53%, 53%)' 30 | 31 | # Related tokens can be grouped together 32 | semantic: 33 | # Color scales (darker or lighter variant of a base color) are 34 | # created automatically and can be referenced by adding a "-" and three 35 | # digits after a token name. 36 | error: '{red-600}' 37 | warning: '{orange-400}' 38 | success: '{green-600}' 39 | 40 | groups: 41 | semantic: 42 | comment: 'These color values are used to convey a meaning' 43 | remarks: '**For more information** about the hidden meaning of semantic colors [read this](https://en.wikipedia.org/wiki/Color_symbolism)' 44 | -------------------------------------------------------------------------------- /.serena/memories/code_style_conventions.md: -------------------------------------------------------------------------------- 1 | # Code Style and Conventions 2 | 3 | ## TypeScript Configuration 4 | - Target: ES2019 5 | - Module: ESNext with Node module resolution 6 | - ESModuleInterop enabled 7 | - No implicit any allowed (noImplicitAny: false) 8 | - Decorators enabled 9 | - Source maps enabled 10 | - Excludes: node_modules, *.spec.ts files, bin directory 11 | 12 | ## ESLint Rules 13 | - Parser: @typescript-eslint/parser 14 | - Extends: @typescript-eslint/recommended, prettier 15 | - Custom rules: 16 | - `@typescript-eslint/no-unused-vars`: warn with _ prefix for ignored args 17 | - `@typescript-eslint/no-explicit-any`: off 18 | - `@typescript-eslint/no-var-requires`: off 19 | - `@typescript-eslint/no-use-before-define`: off 20 | - `indent`: off (handled by Prettier) 21 | 22 | ## Prettier Configuration 23 | - Uses @cortex-js/prettier-config 24 | - Applied to: TS, JS, CSS, MD, YML, JSON files 25 | - Vendor directory excluded 26 | 27 | ## Naming Conventions 28 | - Files: kebab-case (e.g., chromatic-cli.ts, value-parser.ts) 29 | - Interfaces: PascalCase (e.g., Config, Options, TokenFile) 30 | - Functions: camelCase (e.g., chromatic, evaluateTokenExpression, normalizeToken) 31 | - Global variables: g prefix (e.g., gConfig, gTokenValues, gThemes) 32 | - Constants: UPPER_CASE for build constants (e.g., PRODUCTION, BUILD_ID) 33 | 34 | ## Code Organization 35 | - Main entry points: chromatic.ts (API), chromatic-cli.ts (CLI) 36 | - Separate format modules: formats-web.ts, formats-styleguide.ts, formats-generic.ts 37 | - Utility modules: utils.ts, errors.ts, terminal.ts 38 | - Parser modules: value-parser.ts, color-functions.ts 39 | 40 | ## Import Style 41 | - Use ES module imports 42 | - Group imports: Node built-ins, external packages, internal modules 43 | - Example: 44 | ```typescript 45 | import * as fs from 'fs-extra'; 46 | import * as path from 'path'; 47 | import * as yaml from 'yaml'; 48 | import { error, log } from './errors'; 49 | ``` 50 | 51 | ## Error Handling 52 | - Centralized error handling in errors.ts 53 | - Use terminal.ts for colored console output 54 | - Global gIgnoreErrors flag for error suppression option -------------------------------------------------------------------------------- /test/tokens/expressions.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | string: 'boom' 3 | length__12px: '12px' 4 | product-mul__15px: '3 * 5px' 5 | product-mul-multi__30px: '3 * 2 * 5px' 6 | product-div__9pt: '18pt / 2' 7 | term-add: '18pt + 2px' 8 | term-add-multi: '18px + 2px + 10pt' 9 | term-add-multi-angle: '10deg + 5deg + 2deg' 10 | term-sub: '36pt - 10px' 11 | term-zero__36pt: '36pt - 0px' 12 | term-product__13: '5 * 2 + 3' 13 | term-product-2__13: '3 + 5 * 2' 14 | unary-minus__-17: '-17' 15 | unary-plus__17: '+17' 16 | string-plus-string-2: '42 + " is the answer"' 17 | number-plus-number__42: '22 + 20' 18 | angle-plus-angle: '10deg + 5rad' 19 | add-angles-same-unit__18deg: '10deg + 3deg + 5deg' 20 | angle-plus-string: '10deg + " slope"' 21 | percent-plus-percent: '10% + 30%' 22 | percent-plus-string: '10% + " discount"' 23 | color-plus-string: '#fa7 + " color"' 24 | dimen-plus-dimen: '12px + 5pt' 25 | string-plus-number: '"answer: " + 42' 26 | string-plus-angle: '"hello " + 12deg' 27 | string-plus-percent: '"hello " + 10%' 28 | string-plus-color: '"hello " + #f7a' 29 | string-plus-dimen: '"hello " + 12px' 30 | string-plus-string: '"hello " + " world"' 31 | product-angle__20deg: '2 * 10deg' 32 | product-percent__30percent: '2 * 15%' 33 | product-length__18pt: '1.5 * 12pt' 34 | divide-angle__5deg: '10deg / 2' 35 | divide-percent__25percent: '75% / 3' 36 | divide-length__5px: '15px / 3' 37 | group-percent__33-percent: '(100/3)%' 38 | group-length__15px: '(10 + 5)px' 39 | group__25: '5 * (2 + 3)' 40 | assoc-multi__30: '5 * 2 * 3' 41 | assoc-sub__2: '7 - 3 - 2' 42 | length-ratio: '100px / 12pt' 43 | color-ratio__008: '#333 / #aaa' 44 | ws-in-expression__25: ' 5 * ( 2 + 3 ) ' 45 | no-ws-in-expression__25: '5*(2+3)' 46 | ws-in-function: ' mix (#f45 , #324 , 34% ) ' 47 | zero-add-to-length: 12pt + 0 48 | zero-add-to-time: 1s + 0 49 | zero-add-to-angle: 23deg + 0 50 | zero-add-to-frequency: 50hz + 0 51 | invalid: 52 | missing-right-operand: '3 + (5 +' 53 | missing-close-paren: '3 + (5 + 2' 54 | string-unary-neg: '-"hello"' 55 | -------------------------------------------------------------------------------- /examples/advanced/color-scales.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | brown: 'scale(hsl(34deg, 30%, 40%))' 3 | red: 'scale(hsl(4deg, 90%, 50%))' # #F21C0D Red RYB 4 | orange: 'scale(oklch(0.99 0.05 35deg), oklch(0.940 0.330 60deg), oklch(0.530 5 | 0.150 70deg))' # #FF9933 Indian Saffron / Deep Saffron 6 | yellow: 7 | 'scale(oklch(100% 0.1 89deg), oklch(90% 0.6 105deg), oklch(60% 0.18 74deg))' 8 | lime: 'scale(hsl(90deg, 79%, 39%))' # #63B215 Kelly Green 9 | green: 'scale(hsl(130deg, 70%, 43%))' # #17CF36 Vivid Malachite 10 | teal: 11 | 'scale(oklch(95% 0.05 190deg), oklch(0.610 0.400 190deg), oklch(30% 0.2 12 | 190deg))' 13 | cyan: 14 | 'scale(oklch(1.000 0.060 215.0deg), oklch(0.820 0.350 228.0deg), oklch(0.460 15 | 0.180 226.0deg))' 16 | # cyan: 'scale(hsl(199deg, 85%, 50%))' 17 | blue: 'scale(hsl(210deg, 90%, 50%))' # #0D80F2 Tropical Thread / Azure 18 | indigo: 'scale(hsl(260deg 40% 93%), hsl(260deg, 70%, 60%), hsl(260deg, 60%, 19 | 30%))' # #6633CC Strong Violet / Iris 20 | purple: 'scale(hsl(280deg, 80%, 50%))' # #A219E6 Purple X11 21 | magenta: 'scale(hsl(330deg, 80%, 60%))' # #EB4799 Raspberry Pink 22 | 23 | # Dark mode variants: lightness + 0.02, chroma + 0.01 24 | dark: 25 | brown: 'dark-mode(scale(hsl(34deg, 30%, 40%)))' 26 | red: 'dark-mode(scale(hsl(4deg, 90%, 50%)))' 27 | orange: 28 | 'dark-mode(scale(oklch(0.99 0.05 35deg), oklch(0.940 0.330 60deg), 29 | oklch(0.530 0.150 70deg)))' 30 | yellow: 31 | 'dark-mode(scale(oklch(100% 0.1 89deg), oklch(90% 0.6 105deg), oklch(60% 32 | 0.18 74deg)))' 33 | lime: 'dark-mode(scale(hsl(90deg, 79%, 39%)))' 34 | green: 'dark-mode(scale(hsl(130deg, 70%, 43%)))' 35 | teal: 36 | 'dark-mode(scale(oklch(95% 0.05 190deg), oklch(0.610 0.400 190deg), 37 | oklch(30% 0.2 190deg)))' 38 | cyan: 39 | 'dark-mode(scale(oklch(1.000 0.060 215.0deg), oklch(0.820 0.350 228.0deg), 40 | oklch(0.460 0.180 226.0deg)))' 41 | blue: 'dark-mode(scale(hsl(210deg, 90%, 50%)))' 42 | indigo: 43 | 'dark-mode(scale(hsl(260deg 40% 93%), hsl(260deg 70% 60%), hsl(260deg 60% 44 | 30%)))' 45 | purple: 'dark-mode(scale(hsl(280deg, 80%, 50%)))' 46 | magenta: 'dark-mode(scale(hsl(330deg, 80%, 60%)))' 47 | -------------------------------------------------------------------------------- /bin/examples/advanced/color-scales.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | brown: 'scale(hsl(34deg, 30%, 40%))' 3 | red: 'scale(hsl(4deg, 90%, 50%))' # #F21C0D Red RYB 4 | orange: 'scale(oklch(0.99 0.05 35deg), oklch(0.940 0.330 60deg), oklch(0.530 5 | 0.150 70deg))' # #FF9933 Indian Saffron / Deep Saffron 6 | yellow: 7 | 'scale(oklch(100% 0.1 89deg), oklch(90% 0.6 105deg), oklch(60% 0.18 74deg))' 8 | lime: 'scale(hsl(90deg, 79%, 39%))' # #63B215 Kelly Green 9 | green: 'scale(hsl(130deg, 70%, 43%))' # #17CF36 Vivid Malachite 10 | teal: 11 | 'scale(oklch(95% 0.05 190deg), oklch(0.610 0.400 190deg), oklch(30% 0.2 12 | 190deg))' 13 | cyan: 14 | 'scale(oklch(1.000 0.060 215.0deg), oklch(0.820 0.350 228.0deg), oklch(0.460 15 | 0.180 226.0deg))' 16 | # cyan: 'scale(hsl(199deg, 85%, 50%))' 17 | blue: 'scale(hsl(210deg, 90%, 50%))' # #0D80F2 Tropical Thread / Azure 18 | indigo: 'scale(hsl(260deg 40% 93%), hsl(260deg, 70%, 60%), hsl(260deg, 60%, 19 | 30%))' # #6633CC Strong Violet / Iris 20 | purple: 'scale(hsl(280deg, 80%, 50%))' # #A219E6 Purple X11 21 | magenta: 'scale(hsl(330deg, 80%, 60%))' # #EB4799 Raspberry Pink 22 | 23 | # Dark mode variants: lightness + 0.02, chroma + 0.01 24 | dark: 25 | brown: 'dark-mode(scale(hsl(34deg, 30%, 40%)))' 26 | red: 'dark-mode(scale(hsl(4deg, 90%, 50%)))' 27 | orange: 28 | 'dark-mode(scale(oklch(0.99 0.05 35deg), oklch(0.940 0.330 60deg), 29 | oklch(0.530 0.150 70deg)))' 30 | yellow: 31 | 'dark-mode(scale(oklch(100% 0.1 89deg), oklch(90% 0.6 105deg), oklch(60% 32 | 0.18 74deg)))' 33 | lime: 'dark-mode(scale(hsl(90deg, 79%, 39%)))' 34 | green: 'dark-mode(scale(hsl(130deg, 70%, 43%)))' 35 | teal: 36 | 'dark-mode(scale(oklch(95% 0.05 190deg), oklch(0.610 0.400 190deg), 37 | oklch(30% 0.2 190deg)))' 38 | cyan: 39 | 'dark-mode(scale(oklch(1.000 0.060 215.0deg), oklch(0.820 0.350 228.0deg), 40 | oklch(0.460 0.180 226.0deg)))' 41 | blue: 'dark-mode(scale(hsl(210deg, 90%, 50%)))' 42 | indigo: 43 | 'dark-mode(scale(hsl(260deg 40% 93%), hsl(260deg 70% 60%), hsl(260deg 60% 44 | 30%)))' 45 | purple: 'dark-mode(scale(hsl(280deg, 80%, 50%)))' 46 | magenta: 'dark-mode(scale(hsl(330deg, 80%, 60%)))' 47 | -------------------------------------------------------------------------------- /src/terminal.ts: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const ciInfo = require('ci-info'); 3 | 4 | // 5 | // Terminal colors for various kind of messages 6 | // 7 | const tcOrange = '#ffcc00'; 8 | const tcRed = '#fa2040'; 9 | const tcBlue = '#6ab3ff'; 10 | const tcPurple = '#d1d7ff'; 11 | 12 | /** Do not use fancy color output if the output stream is not a terminal 13 | * (e.g. if we're redirecting errors to a log file) or when in a CI environment. 14 | * Note that the debug console in VSCode returns 'undefined' for isTTY. 15 | */ 16 | let gUseColor = (process.stdout.isTTY ?? false) && !ciInfo.isCI; 17 | 18 | export const terminal = { 19 | useColor: (flag: boolean): void => { 20 | gUseColor = flag; 21 | }, 22 | autoFormat: (m: string): string => { 23 | return m 24 | .replace(/("(.*)")/g, (x) => { 25 | return terminal.string(x.slice(1, -1)); 26 | }) 27 | .replace(/(`(.*)`)/g, (x) => { 28 | return terminal.keyword(x); 29 | }); 30 | }, 31 | success: (m = ''): string => { 32 | chalk.green('✔︎ ' + m); 33 | return gUseColor ? chalk.bold.green('✔︎ ' + m) : '✔︎ ' + m; 34 | }, 35 | error: (m = ''): string => { 36 | return gUseColor ? chalk.hex(tcRed)(chalk.bold('✘ ' + m)) : '✘ ' + m; 37 | }, 38 | warning: (m = ''): string => { 39 | return gUseColor 40 | ? chalk.hex(tcOrange)(chalk.bold('⚠️ ' + m)) 41 | : '⚠ ' + m; 42 | }, 43 | path: (m = ''): string => { 44 | return gUseColor ? chalk.hex(tcBlue).italic(m) : m; 45 | }, 46 | keyword: (m = ''): string => { 47 | return gUseColor ? chalk.hex(tcOrange)(m) : m; 48 | }, 49 | string: (m = ''): string => { 50 | return gUseColor 51 | ? chalk.hex(tcOrange)('"' + chalk.italic(m) + '"') 52 | : '"' + m + '"'; 53 | }, 54 | dim: (m = ''): string => { 55 | return gUseColor ? chalk.hex('#999')(m) : m; 56 | }, 57 | time: (t = new Date()): string => { 58 | return gUseColor 59 | ? chalk.hex(tcPurple)(`[${t.toLocaleTimeString()}]`) 60 | : '[' + t + ']'; 61 | }, 62 | link: (m: string): string => { 63 | return gUseColor 64 | ? '\n▷ ' + 65 | chalk.hex(tcPurple)( 66 | 'https://github.com/arnog/chromatic/docs/errors/' + m + '.md' 67 | ) 68 | : '\n▷ https://github.com/arnog/chromatic/docs/errors/' + m + '.md'; 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /examples/advanced/tokens.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | red: 'scale(hsl(4deg, 90%, 50%))' # #F21C0D Red RYB 3 | orange: 'scale(hsl(30deg, 100%, 60%))' # #FF9933 Indian Saffron / Deep Saffron 4 | brown: 'scale(hsl(34deg, 30%, 40%))' 5 | yellow: 'scale(hsl(46deg, 100%, 60%))' # #FFCF33 Peach Cobbler / Sunglow 6 | lime: 'scale(hsl(90deg, 79%, 39%))' # #63B215 Kelly Green 7 | green: 'scale(hsl(130deg, 70%, 43%))' # #17CF36 Vivid Malachite 8 | teal: 'scale(hsl(180deg, 80%, 45%))' # #17CFCF Dark Turquoise 9 | cyan: 'scale(hsl(199deg, 85%, 50%))' 10 | blue: 'scale(hsl(210deg, 90%, 50%))' # #0D80F2 Tropical Thread / Azure 11 | indigo: 'scale(hsl(260deg, 60%, 50%))' # #6633CC Strong Violet / Iris 12 | purple: 'scale(hsl(280deg, 80%, 50%))' # #A219E6 Purple X11 13 | magenta: 'scale(hsl(330deg, 80%, 60%))' # #EB4799 Raspberry Pink 14 | 15 | border-radius: '4px' 16 | 17 | line-height: '18pt + 4px' 18 | 19 | # Defining an angle using the 'deg' unit 20 | primary-hue: 21 | value: 22 | _: '210deg' 23 | dark: '200deg' 24 | 25 | # A token expression can reference another token by enclosing it in "{}" 26 | # Functions can be used to perform more complex calculations 27 | # The 'hsl()' function will return a color based on a hue, saturation and lightness 28 | # Other color functions include "rgb()", "hsv()", "hwb()" and "lab()" 29 | primary: 'hsl({primary-hue}, 100%, 40%)' 30 | cta-button-background: '{primary}' 31 | cta-button-background-active: 'darken({cta-button-background}, 20%)' 32 | cta-button-background-hover: 'darken({cta-button-background}, 10%)' 33 | 34 | # Related tokens can be grouped together 35 | semantic: 36 | # Color scales (darker or lighter variant of a base color) are 37 | # created automatically and can be referenced by adding a "-" and three 38 | # digits after a token name. 39 | error: '{red-600}' 40 | warning: 41 | value: 42 | _: '{orange-400}' 43 | dark: '{orange-500}' 44 | comment: 'Use for problems that do not prevent the task to complete' 45 | success: '{green-600}' 46 | 47 | color-blind: 48 | tritan-1: '#FAFF00' 49 | tritan-2: '#FDF4F8' 50 | protan-1: '#3B7398' 51 | protan-2: '#D81B60' 52 | deuteran-1: '#32F3D9' 53 | deuteran-2: '#F1CFEC' 54 | groups: 55 | semantic: 56 | comment: 'These color values are used to convey a meaning' 57 | remarks: 58 | '**For more information** about the hidden meaning of semantic colors 59 | [read this](https://en.wikipedia.org/wiki/Color_symbolism)' 60 | -------------------------------------------------------------------------------- /bin/examples/advanced/tokens.yaml: -------------------------------------------------------------------------------- 1 | tokens: 2 | red: 'scale(hsl(4deg, 90%, 50%))' # #F21C0D Red RYB 3 | orange: 'scale(hsl(30deg, 100%, 60%))' # #FF9933 Indian Saffron / Deep Saffron 4 | brown: 'scale(hsl(34deg, 30%, 40%))' 5 | yellow: 'scale(hsl(46deg, 100%, 60%))' # #FFCF33 Peach Cobbler / Sunglow 6 | lime: 'scale(hsl(90deg, 79%, 39%))' # #63B215 Kelly Green 7 | green: 'scale(hsl(130deg, 70%, 43%))' # #17CF36 Vivid Malachite 8 | teal: 'scale(hsl(180deg, 80%, 45%))' # #17CFCF Dark Turquoise 9 | cyan: 'scale(hsl(199deg, 85%, 50%))' 10 | blue: 'scale(hsl(210deg, 90%, 50%))' # #0D80F2 Tropical Thread / Azure 11 | indigo: 'scale(hsl(260deg, 60%, 50%))' # #6633CC Strong Violet / Iris 12 | purple: 'scale(hsl(280deg, 80%, 50%))' # #A219E6 Purple X11 13 | magenta: 'scale(hsl(330deg, 80%, 60%))' # #EB4799 Raspberry Pink 14 | 15 | border-radius: '4px' 16 | 17 | line-height: '18pt + 4px' 18 | 19 | # Defining an angle using the 'deg' unit 20 | primary-hue: 21 | value: 22 | _: '210deg' 23 | dark: '200deg' 24 | 25 | # A token expression can reference another token by enclosing it in "{}" 26 | # Functions can be used to perform more complex calculations 27 | # The 'hsl()' function will return a color based on a hue, saturation and lightness 28 | # Other color functions include "rgb()", "hsv()", "hwb()" and "lab()" 29 | primary: 'hsl({primary-hue}, 100%, 40%)' 30 | cta-button-background: '{primary}' 31 | cta-button-background-active: 'darken({cta-button-background}, 20%)' 32 | cta-button-background-hover: 'darken({cta-button-background}, 10%)' 33 | 34 | # Related tokens can be grouped together 35 | semantic: 36 | # Color scales (darker or lighter variant of a base color) are 37 | # created automatically and can be referenced by adding a "-" and three 38 | # digits after a token name. 39 | error: '{red-600}' 40 | warning: 41 | value: 42 | _: '{orange-400}' 43 | dark: '{orange-500}' 44 | comment: 'Use for problems that do not prevent the task to complete' 45 | success: '{green-600}' 46 | 47 | color-blind: 48 | tritan-1: '#FAFF00' 49 | tritan-2: '#FDF4F8' 50 | protan-1: '#3B7398' 51 | protan-2: '#D81B60' 52 | deuteran-1: '#32F3D9' 53 | deuteran-2: '#F1CFEC' 54 | groups: 55 | semantic: 56 | comment: 'These color values are used to convey a meaning' 57 | remarks: 58 | '**For more information** about the hidden meaning of semantic colors 59 | [read this](https://en.wikipedia.org/wiki/Color_symbolism)' 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chromatic 2 | 3 | A tool to help manage design systems by generating platform-specific files from 4 | a source file describing design tokens. 5 | 6 | ### Expressive Design Tokens 7 | 8 | Tokens can contain rich expressions in a natural syntax, including arithmetic 9 | operations, units (`12px`), function (`rgb()`, `mix()`, `saturate()`...) and 10 | references to other tokens. 11 | 12 | ```yaml 13 | tokens: 14 | primary-hue: '210deg' 15 | primary: 'hsl({primary-hue}, 100%, 40%)' 16 | primary-dark: 'darken({primary}, 20%)' 17 | 18 | line-height: '18pt + 5px' 19 | ``` 20 | 21 | ### Themes 22 | 23 | Each token can have a theme variant, such as dark/light, or compact/cozy 24 | layouts. The necessary output artifacts are generated automatically. 25 | 26 | ```yaml 27 | tokens: 28 | cta-button-background: 29 | value: 30 | dark: '#004082' 31 | light: '#0066ce' 32 | ``` 33 | 34 | ### Zero-conf 35 | 36 | Get going quickly. A simple **token file** written YAML or JSON file is all you 37 | need. 38 | 39 | But Chromatic is also customizable when you need to. You can write or modify the 40 | format of the output files to suit your needs. 41 | 42 | Chromatic is also available as an API that can be invoked from a build system. 43 | 44 | ### Multi-platform 45 | 46 | From a single token file, generate platform specific artifacts: 47 | 48 | - for the web (Sass, CSS) 49 | - for iOS (JSON, plist) 50 | - for Android (XML) 51 | 52 | Chromatic can also generate a style guide as a HTML file. 53 | 54 | ## Getting started with Chromatic 55 | 56 | ```shell 57 | $ npm install -g @arnog/chromatic 58 | ``` 59 | 60 | To create a directory with an example: 61 | 62 | ```shell 63 | $ chromatic example ./test 64 | $ chromatic ./test -o tokens.scss 65 | $ chromatic ./test -o tokens.html 66 | ``` 67 | 68 | Or writing your own token file: 69 | 70 | ```yaml 71 | # tokens.yaml 72 | tokens: 73 | background: '#f1f1f1' 74 | body-color: '#333' 75 | ``` 76 | 77 | ```shell 78 | $ chromatic tokens.yaml -o tokens.scss 79 | ``` 80 | 81 | ```scss 82 | $background: #f1f1f1 !default; 83 | $body-color: #333 !default; 84 | ``` 85 | 86 | Now, let's create a dark theme: 87 | 88 | ```yaml 89 | # tokens-dark.yaml 90 | theme: dark 91 | tokens: 92 | background: '#222' 93 | body-color: '#a0a0a0' 94 | ``` 95 | 96 | ```yaml 97 | # tokens.yaml 98 | import: ./tokens-dark.yaml 99 | tokens: 100 | background: '#f1f1f1' 101 | body-color: '#333' 102 | ``` 103 | 104 | ```shell 105 | $ chromatic tokens.yaml -o tokens.scss 106 | ``` 107 | 108 | ```css 109 | :root { 110 | --background: #f1f1f1; 111 | --body-color: #333; 112 | } 113 | body[data-theme='dark'] { 114 | --background: #222; 115 | --body-color: #a0a0a0; 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/functions.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 | ### **`calc`**`(any)` 4 | 5 | Return the argument. This function is here for compatibility with CSS which 6 | requires it when evaluating expressions. However, expressions are evaluated 7 | automatically in Chromatic. 8 | 9 | ### **`rgb`**`(red, green, blue, alpha?)`
**`rgba`**(red, green, blue, alpha?)
**`hsl`**(hue, saturation, lightness, alpha?)
**`hsla`**(hue, saturation, lightness, alpha?)
**`hsv`**(hue, saturation, value, alpha?)
**`hwb`**(hue, whitness, blackness, alpha?)
**`lab`**(l, a, b, alpha?) 10 | 11 | Create a color with the specified color model. The arguments are: 12 | 13 | - `red`, `green`, `blue`: 0..255 14 | - `alpha`: a percentage (e.g. `80%`) or a number 0..1 (e.g. `0.80`). Optional. 15 | - `hue`: an angle (e.g. `230deg`) 16 | - `saturation`, `lightness`, `value`, `whiteness`, blackness 17 | - `l`, `a`, `b`: L: 0..1, a,b: -128..128 18 | 19 | ### **`filter`**`(color, filter)` 20 | 21 | Apply a simple filter to a color. The filter can be one of: 22 | 23 | - `"none"`: return the inpuyt color, unchanged 24 | - `"grayscale"`: return a grayscale version of the input color 25 | - `"deuteranopia"`: green color deficiency (6% of male population) 26 | - `"protanopia"`: red color deficiency (2% of male population) 27 | - `"tritanopia"`: blue color deficiency 28 | 29 | ### 30 | 31 | | | | | 32 | | ------------ | ------------------------------------------------------------ | --- | 33 | | gray() | 'number \| percentage, number \| percentage \| none' | | 34 | | min() | 'any, any' | | 35 | | max() | 'any, any' | | 36 | | clamp() | 'any, any, any' | | 37 | | mix() | 'color, color, number \| percentage \| none, string \| none' | | 38 | | saturate() | 'color, number \| percentage \| none' | | 39 | | desaturate() | 'color, number \| percentage \| none' | | 40 | | lighten() | 'color, number \| percentage \| none' | | 41 | | darken() | 'color, number \| percentage \| none' | | 42 | | rotateHue() | 'color, angle \| number \| none' | | 43 | | complement() | 'color' | | 44 | | contrast() | 'color, color \| none, color \| none' | | 45 | | tint() | 'color, number \| percentage \| none' | | 46 | | shade() | 'color, number \| percentage \| none' | | 47 | | filter() | 'color, string' | | 48 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Style Guide", 11 | "skipFiles": ["/**"], 12 | "preLaunchTask": "npm: build", 13 | "program": "${workspaceFolder}/bin/chromatic", 14 | "args": [ 15 | "./examples/advanced/tokens.yaml", 16 | // "test/tokens/basic-example/tokens.yaml", 17 | "--format", 18 | "html", // html, sass 19 | "--verbose", 20 | "--ignore-errors", 21 | "-o", 22 | "build/style-guide.html" 23 | ] 24 | }, 25 | { 26 | "type": "node", 27 | "request": "launch", 28 | "name": "Color Scale", 29 | "skipFiles": ["/**"], 30 | "preLaunchTask": "npm: build", 31 | "program": "${workspaceFolder}/bin/chromatic", 32 | "args": [ 33 | "./examples/advanced/color-scales.yaml", 34 | // "test/tokens/basic-example/tokens.yaml", 35 | "--format", 36 | "html", // html, sass 37 | "--verbose", 38 | "--ignore-errors", 39 | "-o", 40 | "color-scales.html" 41 | ] 42 | }, 43 | { 44 | "type": "node", 45 | "request": "launch", 46 | "name": "Basic", 47 | "skipFiles": ["/**"], 48 | "program": "${workspaceFolder}/bin/chromatic", 49 | "args": [ 50 | // "./test/tokens/../../examples/advanced/tokens.yaml", 51 | "./test/tokens/colors.yaml", 52 | "--format", 53 | "yaml", 54 | "--header=''", 55 | "--verbose", 56 | "--ignore-errors" 57 | ] 58 | }, 59 | { 60 | "type": "node", 61 | "request": "launch", 62 | "name": "Test", 63 | "skipFiles": ["/**"], 64 | "program": "${workspaceFolder}/bin/chromatic", 65 | "args": ["test/tokens/length.yaml", "--verbose", "--ignore-errors"], 66 | "env": { 67 | // "TEST": "true" 68 | } 69 | }, 70 | { 71 | "type": "node", 72 | "request": "launch", 73 | "name": "Readme", 74 | "skipFiles": ["/**"], 75 | "program": "${workspaceFolder}/bin/chromatic", 76 | "args": ["test/tokens/readme.yaml", "--format='sass'", "--verbose"] 77 | }, 78 | { 79 | "type": "node", 80 | "request": "launch", 81 | "name": "Onboarding", 82 | "skipFiles": ["/**"], 83 | "program": "${workspaceFolder}/bin/chromatic", 84 | "args": ["--verbose"] 85 | }, 86 | { 87 | "type": "node", 88 | "request": "launch", 89 | "name": "Piping", 90 | "skipFiles": ["/**"], 91 | "program": "${workspaceFolder}/bin/chromatic", 92 | "args": ["--verbose"] 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /config/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | import typescript from 'rollup-plugin-typescript2'; 4 | import { eslint } from 'rollup-plugin-eslint'; 5 | import pkg from '.././package.json'; 6 | import copy from 'rollup-plugin-copy'; 7 | 8 | process.env.BUILD = process.env.BUILD || 'development'; 9 | const PRODUCTION = process.env.BUILD === 'production'; 10 | const BUILD_ID = 11 | Date.now().toString(36).slice(-2) + 12 | Math.floor(Math.random() * 0x186a0).toString(36); 13 | 14 | const TYPESCRIPT_OPTIONS = { 15 | typescript: require('typescript'), 16 | // objectHashIgnoreUnknownHack: true, 17 | clean: PRODUCTION, 18 | tsconfigOverride: { 19 | compilerOptions: { 20 | declaration: false, 21 | }, 22 | }, 23 | }; 24 | 25 | const TERSER_OPTIONS = { 26 | compress: { 27 | drop_console: false, 28 | drop_debugger: true, 29 | ecma: 8, // Use "5" to support older browsers 30 | module: true, 31 | warnings: true, 32 | passes: 2, 33 | global_defs: { 34 | ENV: JSON.stringify(process.env.BUILD), 35 | VERSION: JSON.stringify(pkg.version || '0.0'), 36 | BUILD_ID: JSON.stringify(BUILD_ID), 37 | }, 38 | }, 39 | }; 40 | export default [ 41 | { 42 | input: 'src/chromatic-cli.ts', 43 | output: { 44 | file: 'bin/chromatic', 45 | format: 'cjs', 46 | banner: '#!/usr/bin/env node', 47 | sourcemap: !PRODUCTION, 48 | }, 49 | plugins: [ 50 | resolve({ 51 | preferBuiltins: true, 52 | }), 53 | PRODUCTION && eslint(), 54 | typescript(TYPESCRIPT_OPTIONS), 55 | PRODUCTION && terser(TERSER_OPTIONS), 56 | copy({ 57 | targets: [ 58 | { src: 'src/templates', dest: 'bin' }, 59 | { src: 'examples', dest: 'bin' }, 60 | { src: 'package.json', dest: 'bin' }, 61 | ], 62 | }), 63 | ], 64 | watch: { 65 | clearScreen: true, 66 | exclude: 'node_modules/**', 67 | include: ['src/**', 'examples/**'], 68 | }, 69 | }, 70 | { 71 | input: 'src/chromatic.ts', 72 | output: { 73 | file: 'bin/chromatic.js', 74 | format: 'cjs', 75 | sourcemap: !PRODUCTION, 76 | }, 77 | plugins: [ 78 | resolve({ 79 | preferBuiltins: true, 80 | }), 81 | PRODUCTION && eslint(), 82 | typescript(TYPESCRIPT_OPTIONS), 83 | PRODUCTION && terser(TERSER_OPTIONS), 84 | ], 85 | watch: { 86 | clearScreen: false, 87 | exclude: ['node_modules/**'], 88 | }, 89 | }, 90 | ]; 91 | 92 | /* 93 | amd – Asynchronous Module Definition, used with module loaders like RequireJS 94 | cjs – CommonJS, suitable for Node and other bundlers 95 | esm – Keep the bundle as an ES module file, suitable for other bundlers and inclusion as a 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /src/templates/html-file.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#if header}} 4 | 5 | {{/if}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {{!-- 17 | --}} 18 | 19 | 20 | 320 | 321 | 322 | 323 | 324 | {{#if color-section }} 325 |
326 |

Colors

327 | {{{ color-section }}} 328 |
329 | {{/if }} 330 | 331 | 332 | 333 | 334 | 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /docs/token-value.md: -------------------------------------------------------------------------------- 1 | # Token Value 2 | 3 | A token value can contain: 4 | 5 | - A simple value such as `12px` or`#9370db` 6 | - An alias, a reference to another token: `{brand.color}` 7 | - An expression, a set of literals, operators and function calls that evaluate 8 | to a single value. 9 | 10 | For example: 11 | 12 | ```yaml 13 | tokens: 14 | primary-background: 'darken(lab(54.975%, 36.8, -50.097))' 15 | ``` 16 | 17 | ## Aliases 18 | 19 | Aliases reference the value of another token. They are enclosed in curly 20 | brackets and include a dot-separated 'path' to the token they reference. 21 | 22 | ```yaml 23 | tokens: 24 | brand: 25 | primary: 'LightGoldenRodYellow' 26 | secondary: 'DarkTurquoise' 27 | background: '{brand.primary}' 28 | ``` 29 | 30 | ## Literals 31 | 32 | The following types of literals can be used: 33 | 34 | | Type | | | 35 | | ----------- | ------------- | -------------------------------------------------- | ---- | --- | ----- | --- | --- | ---- | ----- | 36 | | **number** | `3.54` | A floating point, unitless, number | 37 | | **angle** | `12deg` | Units: `deg | grad | rad | turn` | 38 | | **percent** | `25%` | A number followed by a `%` | 39 | | **color** | `#fc374a` | A set of 3, 4, 6 or 8 hex digits preceded by a `#` | 40 | | **length** | `3em` | Relative units: `em | ex | ch | rem | vw | vh | vmin | vmax` | 41 | | | `3px` | Absolute units: `cm | mm | Q | in | pc | pt | px` | 42 | | **string** | `"Helvetica"` | Enclosed in double-quotes | 43 | 44 | Some arithmetic operations can be performed between literals, if their types are 45 | compatibl. For example, you can't divide a color by a length, but you can 46 | multiply a number by a length. 47 | 48 | Relative units cannot be evaluated (since their value is not known until 49 | rendering time). However, although `rem` is a relative unit, its value can be 50 | 'fixed' by defining the `base-font-size` property in the configuration file, in 51 | pixels. 52 | 53 | ```yaml 54 | tokens: 55 | length: '36pt + 5px' 56 | angle: '.5turn - 10deg' 57 | percent: '(100 / 3)%' 58 | string: '"The answer is " + (84 / 2)' 59 | ``` 60 | 61 | ## Functions 62 | 63 | | Name | Arguments | | 64 | | -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 65 | | **rgb** | `(red, green, blue [, alpha])` | Each color channel should be in the [0...255] range. The alpha value is either a number in the [0...1] range, or a percentage | 66 | | **hsl** | `(h, s, l, [, alpha])` | The hue is an angle (or a number in the [0...360] range), the saturation and lightness are numbers in the [0...1] range or percentages | 67 | | **hsv** | `(h, s, b, [, alpha]` | The hue is an angle (or a number in the [0...360] range), the saturation and brightness are numbers in the [0...1] range or percentages. Note that the **hsv\* color model is also called **hsb\*\* | 68 | | **hwb** | `(h, w, b, [, alpha])` | The hue is an angle (or a number in the [0...360] range), the whiteness and blackness are numbers in the [0...1] range or percentages. | 69 | | **lab** | `(l, a, b, [, alpha])` | The lightness is a number in the [0...1] range or a percentage. The a and b arguments are numbers, theoretically unbounded but in practice in the [-160...160] range | 70 | | **gray** | `(g, [, alpha])` | The g argument is a number in the [0..1] range or a percentage. It is defined such that `gray(50%)` is a visual mid-gray, perceptrually equidistant between black and white. It is equivalent to `lab(g, 0, 0)` | 71 | | **min** | `(a, b)` | The smallest of the two arguments | 72 | | **max** | `(a, b)` | The largest of the two arguments | 73 | | **clamp** | `(low, x, high)` | If `x` is less than `low`, return `low`. If x is greater than `high`, return `high` | 74 | | **mix** | `(c1, c2, [weight [, model]])` | Combine the two colors c1 and c2, according to the weight specified (a number in the [0...1] range or a percentage. If no weight is provided, the colors are mixed equally. The model is 'rgb' by default, but 'hsl' can be used as well | 75 | | **tint** | `(c[, w])` | Mix the color c with white. If w is not provided, defaults to 10% | 76 | | **shade** | `(c[, w])` | Mix the color c with black. If w is not provided, defaults to 10% | 77 | | **saturate** | `(c, v)` | Increase the saturation of the color c. The second argument is a number in the range [0...1] or a percentage. The increase is relative, not absolute. So if a color has a 50% saturation, increasing the saturation by 10% will yield a new color with a saturation of 55% (not 60%) | 78 | | **desaturate** | `(c, v)` | The opposite of **saturate**. | 79 | | **lighten** | `(c, v)` | Increase the ligthness of a color. Like **saturate**, the increase is relative. | 80 | | **darken** | `(c, v)` | The opposite of **lighten**. | 81 | | **rotateHue** | `(c, v)` | Change the hue by adding (or substracting) the value v, which must be a number in the [-180, +180] range. | 82 | | **complement** | `(c)` | Provide the complementary color to c. Equivalent to `rotateHue(c, 180)` | 83 | | **contrast** | `(base, [dark, light])` | Return either the dark or the light color, whichever has the highest contrast on the base color. If dark and light are not provided, they default to black and white. | 84 | 85 | The `alpha` value is a number in the [0...1] range or a percentage. 86 | -------------------------------------------------------------------------------- /bin/templates/html-colors.hbs: -------------------------------------------------------------------------------- 1 | {{#if group}}

{{ group }}

{{/if}} 2 | 3 |
4 | {{#each colorRamps}} 5 |
6 |

{{name}}

7 | 8 | 9 |
10 |

Lightness Curve (OKLCh)

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1.0 20 | 0.5 21 | 0.0 22 | 50 23 | 900 24 | Scale Index 25 | Lightness 26 | 27 | 28 | 29 | 30 | 31 | {{#each lightnessData}} 32 | 33 | {{index}}: L={{lightness}} 34 | 35 | {{/each}} 36 | 37 |
38 | 39 | 40 |
41 |

Hue/Chroma Distribution (OKLCh)

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {{#if hueChromaData.[0].maxChroma}} 50 | Max C: {{hueChromaData.[0].maxChroma}} 51 | {{/if}} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 90° 61 | 180° 62 | 270° 63 | Hue 64 | Chroma 65 | 66 | 67 | {{#each hueChromaData}} 68 | 69 | {{index}}: H={{hue}}° C={{chroma}} L={{lightness}} 70 | 71 | {{/each}} 72 | 73 |
74 | 75 |
76 | {{#each values}} 77 |
78 |

{{name}}

79 | {{#if deltaE }}{{/if}} 80 |

{{css}}

81 | {{#if deltaE }}δE {{deltaE}}{{/if}} 82 |
83 | {{/each}} 84 |
85 |

{{source}}

86 |
87 | {{/each}} 88 |
89 | 90 |
91 | {{#each colors}} 92 |
93 |
94 |
95 |

{{name}}

96 |

{{css}}

97 |
98 |
99 | {{#if source}} 100 |

{{source}}

101 | {{/if}} 102 | {{#if comment}} 103 |

{{{comment}}}

104 | {{/if}} 105 | {{!--
    106 |
  • 107 | 108 | 109 |
  • 110 |
  • 111 | 112 | 113 |
  • 114 |
  • 115 | 116 | 117 |
  • 118 |
--}} 119 | {{#if similarColors}} 120 |
121 | {{#if similarColors.normal }} 122 |

Similar Colors

123 |
    124 | {{#each similarColors.normal }} 125 |
  • 126 | 127 | {{name}} 128 | 129 | δE {{deltaE}} 130 |
  • 131 | {{/each}} 132 |
133 | {{/if}} 134 | {{!-- {{#if similarColors.protanopia }} 135 |

Similar Colors for People with Protanopia

136 |
    137 | {{#each similarColors.protanopia }} 138 |
  • 139 | 140 | {{name}} 141 | 142 | δE {{deltaE}} 143 |
  • 144 | {{/each}} 145 |
146 | {{/if}} 147 | {{#if similarColors.deuteranopia }} 148 |

Similar Colors for People with Deuteranopia

149 |
    150 | {{#each similarColors.deuteranopia }} 151 |
  • 152 | 153 | {{name}} 154 | 155 | δE {{deltaE}} 156 |
  • 157 | {{/each}} 158 |
159 | {{/if}} 160 | {{#if similarColors.tritanopia }} 161 |

Similar Colors for People with Tritanopia

162 |
    163 | {{#each similarColors.tritanopia }} 164 |
  • 165 | 166 | {{name}} 167 | 168 | δE {{deltaE}} 169 |
  • 170 | {{/each}} 171 |
172 | {{/if}} --}} 173 | {{#if similarColors.colorDeficient }} 174 |

Similar Colors for People with Color Deficiency

175 |
    176 | {{#each similarColors.colorDeficient }} 177 |
  • 178 | 179 | {{name}} 180 | 181 |
  • 182 | {{/each}} 183 |
184 | {{/if}} 185 |
186 | {{/if}} 187 |
188 | {{/each}} 189 |
-------------------------------------------------------------------------------- /src/templates/html-colors.hbs: -------------------------------------------------------------------------------- 1 | {{#if group}}

{{ group }}

{{/if}} 2 | 3 |
4 | {{#each colorRamps}} 5 |
6 |

{{name}}

7 | 8 | 9 |
10 |

Lightness Curve (OKLCh)

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 1.0 20 | 0.5 21 | 0.0 22 | 50 23 | 900 24 | Scale Index 25 | Lightness 26 | 27 | 28 | 29 | 30 | 31 | {{#each lightnessData}} 32 | 33 | {{index}}: L={{lightness}} 34 | 35 | {{/each}} 36 | 37 |
38 | 39 | 40 |
41 |

Hue/Chroma Distribution (OKLCh)

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {{#if hueChromaData.[0].maxChroma}} 50 | Max C: {{hueChromaData.[0].maxChroma}} 51 | {{/if}} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 90° 61 | 180° 62 | 270° 63 | Hue 64 | Chroma 65 | 66 | 67 | {{#each hueChromaData}} 68 | 69 | {{index}}: H={{hue}}° C={{chroma}} L={{lightness}} 70 | 71 | {{/each}} 72 | 73 |
74 | 75 |
76 | {{#each values}} 77 |
78 |

{{name}}

79 | {{#if deltaE }}{{/if}} 80 |

{{css}}

81 | {{#if deltaE }}δE {{deltaE}}{{/if}} 82 |
83 | {{/each}} 84 |
85 |

{{source}}

86 |
87 | {{/each}} 88 |
89 | 90 |
91 | {{#each colors}} 92 |
93 |
94 |
95 |

{{name}}

96 |

{{css}}

97 |
98 |
99 | {{#if source}} 100 |

{{source}}

101 | {{/if}} 102 | {{#if comment}} 103 |

{{{comment}}}

104 | {{/if}} 105 | {{!--
    106 |
  • 107 | 108 | 109 |
  • 110 |
  • 111 | 112 | 113 |
  • 114 |
  • 115 | 116 | 117 |
  • 118 |
--}} 119 | {{#if similarColors}} 120 |
121 | {{#if similarColors.normal }} 122 |

Similar Colors

123 |
    124 | {{#each similarColors.normal }} 125 |
  • 126 | 127 | {{name}} 128 | 129 | δE {{deltaE}} 130 |
  • 131 | {{/each}} 132 |
133 | {{/if}} 134 | {{!-- {{#if similarColors.protanopia }} 135 |

Similar Colors for People with Protanopia

136 |
    137 | {{#each similarColors.protanopia }} 138 |
  • 139 | 140 | {{name}} 141 | 142 | δE {{deltaE}} 143 |
  • 144 | {{/each}} 145 |
146 | {{/if}} 147 | {{#if similarColors.deuteranopia }} 148 |

Similar Colors for People with Deuteranopia

149 |
    150 | {{#each similarColors.deuteranopia }} 151 |
  • 152 | 153 | {{name}} 154 | 155 | δE {{deltaE}} 156 |
  • 157 | {{/each}} 158 |
159 | {{/if}} 160 | {{#if similarColors.tritanopia }} 161 |

Similar Colors for People with Tritanopia

162 |
    163 | {{#each similarColors.tritanopia }} 164 |
  • 165 | 166 | {{name}} 167 | 168 | δE {{deltaE}} 169 |
  • 170 | {{/each}} 171 |
172 | {{/if}} --}} 173 | {{#if similarColors.colorDeficient }} 174 |

Similar Colors for People with Color Deficiency

175 |
    176 | {{#each similarColors.colorDeficient }} 177 |
  • 178 | 179 | {{name}} 180 | 181 |
  • 182 | {{/each}} 183 |
184 | {{/if}} 185 |
186 | {{/if}} 187 |
188 | {{/each}} 189 |
-------------------------------------------------------------------------------- /src/formats-styleguide.ts: -------------------------------------------------------------------------------- 1 | const marked = require('marked'); 2 | const highlight = require('highlight.js'); 3 | const handlebars = require('handlebars'); 4 | const fs = require('fs'); 5 | const chroma = require('chroma-js'); 6 | 7 | import { Color, isColor, isColorArray, roundTo } from './value'; 8 | import { getSimilarColors, getDeltaE, filterColor } from './color-functions'; 9 | import { RenderContext, Format } from './formats'; 10 | 11 | function renderColorSection(context: RenderContext): string { 12 | let result = ''; 13 | const handlebarsContext = { colors: [], colorRamps: [], group: '' }; 14 | const allColors: { name: string; color: Color }[] = []; 15 | context.themes.forEach((theme) => { 16 | theme.tokens.forEach((token) => { 17 | if (isColor(token.tokenValue)) 18 | allColors.push({ 19 | name: 20 | token.tokenId + 21 | (theme.theme === '_' || theme.theme === '' 22 | ? '' 23 | : '.' + theme.theme), 24 | color: token.tokenValue, 25 | }); 26 | }); 27 | }); 28 | 29 | context.themes.forEach((theme) => { 30 | handlebarsContext.group = 31 | context.themes.length === 1 32 | ? '' 33 | : theme.theme === '_' 34 | ? 'Base' 35 | : theme.theme; 36 | handlebarsContext.colors = []; 37 | theme.tokens.forEach((token) => { 38 | if (isColor(token.tokenValue)) { 39 | const color = token.tokenValue as Color; 40 | let cls = color.luma() >= 1.0 ? 'frame ' : ''; 41 | if (color.luma() > 0.42) cls += 'light'; 42 | let opaqueColor: Color; 43 | if (color.a < 1.0) { 44 | opaqueColor = new Color(color); 45 | opaqueColor.a = 1.0; 46 | } 47 | const similarColors = getSimilarColors(color, allColors); 48 | const similarProtanopiaColors = getSimilarColors( 49 | color, 50 | allColors, 51 | 'protanopia' 52 | ); 53 | const similarDeuteranopiaColors = getSimilarColors( 54 | color, 55 | allColors, 56 | 'deuteranopia' 57 | )?.filter((x) => !similarColors?.includes(x)); 58 | const similarTritanopiaColors = getSimilarColors( 59 | color, 60 | allColors, 61 | 'tritanopia' 62 | ); 63 | const similarColorsColorDeficient = []; 64 | [ 65 | ...(similarDeuteranopiaColors ?? []), 66 | ...(similarTritanopiaColors ?? []), 67 | ...(similarProtanopiaColors ?? []), 68 | ].forEach((x) => { 69 | // Add to the list if it's not: 70 | // 1/ already in the list and 71 | // 2/ not in the "normal" similar colors 72 | // We want this list to only include colors that are similar 73 | // for people with color deficiency 74 | if ( 75 | similarColorsColorDeficient.findIndex((y) => y.name === x.name) < 0 76 | ) { 77 | if ( 78 | !similarColors || 79 | similarColors.findIndex((y) => y.name === x.name) < 0 80 | ) { 81 | similarColorsColorDeficient.push(x); 82 | } 83 | } 84 | }); 85 | handlebarsContext.colors.push({ 86 | name: token.tokenId, 87 | value: token.tokenValue, 88 | source: color.getSource(), 89 | css: color.css(), 90 | protanopiaCss: filterColor(color, 'protanopia').css(), 91 | deuteranopiaCss: filterColor(color, 'deuteranopia').css(), 92 | tritanopiaCss: filterColor(color, 'tritanopia').css(), 93 | comment: token.tokenDefinition.comment ?? '', 94 | cls, 95 | opaqueColor: opaqueColor?.css(), 96 | similarColors: { 97 | normal: similarColors 98 | ? similarColors.map((x) => { 99 | return { 100 | name: x.name, 101 | css: x.color.css(), 102 | deltaE: roundTo(x.deltaE, 2), 103 | }; 104 | }) 105 | : null, 106 | colorDeficient: similarColorsColorDeficient 107 | ? similarColorsColorDeficient.map((x) => { 108 | return { 109 | name: x.name, 110 | css: x.color.css(), 111 | deltaE: roundTo(x.deltaE, 2), 112 | }; 113 | }) 114 | : null, 115 | protanopia: similarProtanopiaColors 116 | ? similarProtanopiaColors.map((x) => { 117 | return { 118 | name: x.name, 119 | css: x.color.css(), 120 | deltaE: roundTo(x.deltaE, 2), 121 | }; 122 | }) 123 | : null, 124 | deuteranopia: similarDeuteranopiaColors 125 | ? similarDeuteranopiaColors.map((x) => { 126 | return { 127 | name: x.name, 128 | css: x.color.css(), 129 | deltaE: roundTo(x.deltaE, 2), 130 | }; 131 | }) 132 | : null, 133 | tritanopia: similarTritanopiaColors 134 | ? similarTritanopiaColors.map((x) => { 135 | return { 136 | name: x.name, 137 | css: x.color.css(), 138 | deltaE: roundTo(x.deltaE, 2), 139 | }; 140 | }) 141 | : null, 142 | }, 143 | }); 144 | } else if (isColorArray(token.tokenValue)) { 145 | let previousColor; 146 | 147 | // Generate visualization data for color ramps 148 | const rampColors = token.tokenValue.value.map((x) => x as Color); 149 | const lightnessData = rampColors.map((color, index) => { 150 | const oklch = chroma(color.hex()).oklch(); 151 | const lightness = oklch[0] || 0; 152 | const chroma_val = oklch[1] || 0; 153 | const hue = oklch[2] || 0; 154 | 155 | // Calculate positions for lightness curve 156 | const x = (index / (rampColors.length - 1)) * 300; 157 | const maxLightness = 1.0; 158 | const minLightness = 0.0; 159 | const y = 150 - ((lightness - minLightness) / (maxLightness - minLightness)) * 120; 160 | 161 | return { 162 | index: index === 0 ? 50 : index * 100, 163 | lightness: Math.round(lightness * 1000) / 1000, 164 | chroma: Math.round(chroma_val * 1000) / 1000, 165 | hue: Math.round(hue), 166 | color: color.hex(), 167 | x: x, 168 | y: y, 169 | }; 170 | }); 171 | 172 | // Generate SVG polyline points for lightness curve 173 | const lightnessCurve = lightnessData.map((d) => `${d.x},${d.y}`).join(' '); 174 | 175 | // Generate hue/chroma chart data with improved scaling 176 | // Find max chroma to scale appropriately 177 | const maxChroma = Math.max(...lightnessData.map(d => d.chroma || 0)); 178 | const chromaScale = maxChroma > 0 ? Math.min(300, 90 / maxChroma) : 300; 179 | 180 | const hueChromaData = lightnessData.map((d) => { 181 | const radius = (d.chroma || 0) * chromaScale; 182 | const angle = (((d.hue || 0) - 90) * Math.PI) / 180; 183 | const x = 110 + Math.cos(angle) * radius; 184 | const y = 110 - Math.sin(angle) * radius; 185 | 186 | return { 187 | index: d.index, 188 | x: Math.round(x), 189 | y: Math.round(y), 190 | color: d.color, 191 | lightness: d.lightness, 192 | chroma: d.chroma, 193 | hue: d.hue, 194 | maxChroma: maxChroma, // Include for debugging 195 | }; 196 | }); 197 | 198 | handlebarsContext.colorRamps.push({ 199 | name: token.tokenId, 200 | source: token.tokenValue.getSource(), 201 | lightnessData: lightnessData, 202 | lightnessCurve: lightnessCurve, 203 | hueChromaData: hueChromaData, 204 | values: token.tokenValue.value.map((x, i) => { 205 | const color = x as Color; 206 | let cls = color.luma() >= 1.0 ? 'frame ' : ''; 207 | if (color.luma() > 0.42) cls += 'light'; 208 | let opaqueColor: Color; 209 | if (color.a < 1.0) { 210 | opaqueColor = new Color(color); 211 | opaqueColor.a = 1.0; 212 | } 213 | const deltaEWithPrevious = 214 | previousColor && getDeltaE(color, previousColor); 215 | previousColor = color; 216 | return { 217 | name: i === 0 ? '50' : i * 100, 218 | cls, 219 | value: color, 220 | css: color.css(), 221 | opaqueColor: opaqueColor?.css(), 222 | deltaE: 223 | deltaEWithPrevious < 2 224 | ? roundTo(deltaEWithPrevious, 2) 225 | : undefined, 226 | }; 227 | }), 228 | }); 229 | } 230 | }); 231 | result += handlebars.compile( 232 | fs.readFileSync(__dirname + '/templates/html-colors.hbs', 'utf-8') 233 | )(handlebarsContext); 234 | }); 235 | 236 | return result; 237 | } 238 | 239 | export const StyleGuideFormat: { formats: { [key: string]: Format } } = { 240 | formats: { 241 | 'html/colors': { 242 | ext: '.html', 243 | render: renderColorSection, 244 | }, 245 | 'html': { 246 | ext: '.html', 247 | render: (context: RenderContext): string => 248 | context.renderTemplate( 249 | fs.readFileSync(__dirname + '/templates/html-file.hbs', 'utf-8'), 250 | { ...context, 'color-section': renderColorSection(context) } 251 | ), 252 | }, 253 | }, 254 | }; 255 | 256 | marked.setOptions({ 257 | renderer: new marked.Renderer(), 258 | highlight: (code: string): string => highlight.highlightAuto(code).value, 259 | pedantic: false, 260 | gfm: true, 261 | breaks: false, 262 | sanitize: false, 263 | smartLists: true, 264 | smartypants: false, 265 | xhtml: false, 266 | }); 267 | -------------------------------------------------------------------------------- /color-scales.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was automatically generated by Chromatic. 3 | * Do not edit. 4 | * Generated 2025-08-12T23:53:59.485Z 5 | * 6 | */ 7 | 8 | $brown: (#f6ebde, #decfbe, #c4b19b, #aa9478, #977f60, #856a47, #765b39, #674d2b, #533b18, #412904) !default; 9 | $brown-50: #f6ebde !default; 10 | $brown-100: #decfbe !default; 11 | $brown-200: #c4b19b !default; 12 | $brown-300: #aa9478 !default; 13 | $brown-400: #977f60 !default; 14 | $brown-500: #856a47 !default; 15 | $brown-600: #765b39 !default; 16 | $brown-700: #674d2b !default; 17 | $brown-800: #533b18 !default; 18 | $brown-900: #412904 !default; 19 | $red: (#ffdcd0, #ffbcac, #ff9785, #ff705d, #fd4f3c, #f21c0d, #db0000, #c40000, #a60000, #800) !default; 20 | $red-50: #ffdcd0 !default; 21 | $red-100: #ffbcac !default; 22 | $red-200: #ff9785 !default; 23 | $red-300: #ff705d !default; 24 | $red-400: #fd4f3c !default; 25 | $red-500: #f21c0d !default; 26 | $red-600: #db0000 !default; 27 | $red-700: #c40000 !default; 28 | $red-800: #a60000 !default; 29 | $red-900: #800 !default; 30 | $orange: (#fff1e5, #ffdfc0, #ffcc9a, #ffb971, #ffa63c, #f49401, #df8402, #ca7402, #b66501, #a15700) !default; 31 | $orange-50: #fff1e5 !default; 32 | $orange-100: #ffdfc0 !default; 33 | $orange-200: #ffcc9a !default; 34 | $orange-300: #ffb971 !default; 35 | $orange-400: #ffa63c !default; 36 | $orange-500: #f49401 !default; 37 | $orange-600: #df8402 !default; 38 | $orange-700: #ca7402 !default; 39 | $orange-800: #b66501 !default; 40 | $orange-900: #a15700 !default; 41 | $yellow: (#fffdb0, #fdf497, #fdeb7c, #fde15d, #fed733, #fac400, #eeac00, #df9400, #cf7e00, #bd6900) !default; 42 | $yellow-50: #fffdb0 !default; 43 | $yellow-100: #fdf497 !default; 44 | $yellow-200: #fdeb7c !default; 45 | $yellow-300: #fde15d !default; 46 | $yellow-400: #fed733 !default; 47 | $yellow-500: #fac400 !default; 48 | $yellow-600: #eeac00 !default; 49 | $yellow-700: #df9400 !default; 50 | $yellow-800: #cf7e00 !default; 51 | $yellow-900: #bd6900 !default; 52 | $lime: (#e1fdd1, #c7eeb0, #abdd8c, #8ecc66, #79bf45, #63b215, #4d9b00, #368400, #156600, #004a00) !default; 53 | $lime-50: #e1fdd1 !default; 54 | $lime-100: #c7eeb0 !default; 55 | $lime-200: #abdd8c !default; 56 | $lime-300: #8ecc66 !default; 57 | $lime-400: #79bf45 !default; 58 | $lime-500: #63b215 !default; 59 | $lime-600: #4d9b00 !default; 60 | $lime-700: #368400 !default; 61 | $lime-800: #156600 !default; 62 | $lime-900: #004a00 !default; 63 | $green: (#d6ffd6, #b8f2b8, #95e296, #6fd274, #4fc659, #21ba3a, #00a21c, #008b00, #006d00, #005000) !default; 64 | $green-50: #d6ffd6 !default; 65 | $green-100: #b8f2b8 !default; 66 | $green-200: #95e296 !default; 67 | $green-300: #6fd274 !default; 68 | $green-400: #4fc659 !default; 69 | $green-500: #21ba3a !default; 70 | $green-600: #00a21c !default; 71 | $green-700: #008b00 !default; 72 | $green-800: #006d00 !default; 73 | $green-900: #005000 !default; 74 | $teal: (#c9faf6, #aaece8, #8adeda, #66d0cc, #37c2bf, #00adaa, #00918f, #007775, #005d5c, #004544) !default; 75 | $teal-50: #c9faf6 !default; 76 | $teal-100: #aaece8 !default; 77 | $teal-200: #8adeda !default; 78 | $teal-300: #66d0cc !default; 79 | $teal-400: #37c2bf !default; 80 | $teal-500: #00adaa !default; 81 | $teal-600: #00918f !default; 82 | $teal-700: #007775 !default; 83 | $teal-800: #005d5c !default; 84 | $teal-900: #004544 !default; 85 | $cyan: (#d2ffff, #b4f9fc, #94f2fb, #70ebfc, #3ee2fe, #00d0f6, #00b4e3, #0099cf, #007fb9, #0065a3) !default; 86 | $cyan-50: #d2ffff !default; 87 | $cyan-100: #b4f9fc !default; 88 | $cyan-200: #94f2fb !default; 89 | $cyan-300: #70ebfc !default; 90 | $cyan-400: #3ee2fe !default; 91 | $cyan-500: #00d0f6 !default; 92 | $cyan-600: #00b4e3 !default; 93 | $cyan-700: #0099cf !default; 94 | $cyan-800: #007fb9 !default; 95 | $cyan-900: #0065a3 !default; 96 | $blue: (#d2f2ff, #afdcff, #88c2ff, #60a8ff, #3f94fa, #0d80f2, #006ddd, #0059c8, #003fad, #002392) !default; 97 | $blue-50: #d2f2ff !default; 98 | $blue-100: #afdcff !default; 99 | $blue-200: #88c2ff !default; 100 | $blue-300: #60a8ff !default; 101 | $blue-400: #3f94fa !default; 102 | $blue-500: #0d80f2 !default; 103 | $blue-600: #006ddd !default; 104 | $blue-700: #0059c8 !default; 105 | $blue-800: #003fad !default; 106 | $blue-900: #002392 !default; 107 | $indigo: (#ebe6f4, #d4c6f1, #bca6ed, #a486e8, #8d64e3, #794cd4, #6941bd, #5a35a6, #4b2a90, #3d1f7a) !default; 108 | $indigo-50: #ebe6f4 !default; 109 | $indigo-100: #d4c6f1 !default; 110 | $indigo-200: #bca6ed !default; 111 | $indigo-300: #a486e8 !default; 112 | $indigo-400: #8d64e3 !default; 113 | $indigo-500: #794cd4 !default; 114 | $indigo-600: #6941bd !default; 115 | $indigo-700: #5a35a6 !default; 116 | $indigo-800: #4b2a90 !default; 117 | $indigo-900: #3d1f7a !default; 118 | $purple: (#fdf, #ecbcff, #d796ff, #c26efb, #b24cf1, #a219e6, #9100d3, #8100c1, #6c00a9, #570091) !default; 119 | $purple-50: #fdf !default; 120 | $purple-100: #ecbcff !default; 121 | $purple-200: #d796ff !default; 122 | $purple-300: #c26efb !default; 123 | $purple-400: #b24cf1 !default; 124 | $purple-500: #a219e6 !default; 125 | $purple-600: #9100d3 !default; 126 | $purple-700: #8100c1 !default; 127 | $purple-800: #6c00a9 !default; 128 | $purple-900: #570091 !default; 129 | $magenta: (#ffdef5, #ffc1e1, #ffa1cc, #fe80b7, #f565a8, #eb4799, #d32d84, #ba0270, #9a0057, #7b003e) !default; 130 | $magenta-50: #ffdef5 !default; 131 | $magenta-100: #ffc1e1 !default; 132 | $magenta-200: #ffa1cc !default; 133 | $magenta-300: #fe80b7 !default; 134 | $magenta-400: #f565a8 !default; 135 | $magenta-500: #eb4799 !default; 136 | $magenta-600: #d32d84 !default; 137 | $magenta-700: #ba0270 !default; 138 | $magenta-800: #9a0057 !default; 139 | $magenta-900: #7b003e !default; 140 | $dark-brown: (#fff1dd, #e9d5bd, #cfb69a, #b49977, #a1845e, #8e6f45, #7f5f37, #705128, #5c3f13, #492d00) !default; 141 | $dark-brown-50: #fff1dd !default; 142 | $dark-brown-100: #e9d5bd !default; 143 | $dark-brown-200: #cfb69a !default; 144 | $dark-brown-300: #b49977 !default; 145 | $dark-brown-400: #a1845e !default; 146 | $dark-brown-500: #8e6f45 !default; 147 | $dark-brown-600: #7f5f37 !default; 148 | $dark-brown-700: #705128 !default; 149 | $dark-brown-800: #5c3f13 !default; 150 | $dark-brown-900: #492d00 !default; 151 | $dark-red: (#ffe0d2, #ffc0ae, #ff9a87, #ff725e, #ff503c, #fd1a09, #e60000, #cf0000, #b10000, #920000) !default; 152 | $dark-red-50: #ffe0d2 !default; 153 | $dark-red-100: #ffc0ae !default; 154 | $dark-red-200: #ff9a87 !default; 155 | $dark-red-300: #ff725e !default; 156 | $dark-red-400: #ff503c !default; 157 | $dark-red-500: #fd1a09 !default; 158 | $dark-red-600: #e60000 !default; 159 | $dark-red-700: #cf0000 !default; 160 | $dark-red-800: #b10000 !default; 161 | $dark-red-900: #920000 !default; 162 | $dark-orange: (#fff6e5, #ffe4bf, #ffd198, #ffbe6e, #ffaa34, #ff9800, #ea8800, #d47800, #c06900, #ab5a00) !default; 163 | $dark-orange-50: #fff6e5 !default; 164 | $dark-orange-100: #ffe4bf !default; 165 | $dark-orange-200: #ffd198 !default; 166 | $dark-orange-300: #ffbe6e !default; 167 | $dark-orange-400: #ffaa34 !default; 168 | $dark-orange-500: #ff9800 !default; 169 | $dark-orange-600: #ea8800 !default; 170 | $dark-orange-700: #d47800 !default; 171 | $dark-orange-800: #c06900 !default; 172 | $dark-orange-900: #ab5a00 !default; 173 | $dark-yellow: (#ffffae, #fffb94, #fff278, #ffe757, #ffdd24, #ffca00, #f8b100, #e99900, #d98200, #c76d00) !default; 174 | $dark-yellow-50: #ffffae !default; 175 | $dark-yellow-100: #fffb94 !default; 176 | $dark-yellow-200: #fff278 !default; 177 | $dark-yellow-300: #ffe757 !default; 178 | $dark-yellow-400: #ffdd24 !default; 179 | $dark-yellow-500: #ffca00 !default; 180 | $dark-yellow-600: #f8b100 !default; 181 | $dark-yellow-700: #e99900 !default; 182 | $dark-yellow-800: #d98200 !default; 183 | $dark-yellow-900: #c76d00 !default; 184 | $dark-lime: (#e5ffd2, #cbf6b1, #aee58c, #91d465, #7cc743, #65b909, #4fa200, #378b00, #136d00, #005100) !default; 185 | $dark-lime-50: #e5ffd2 !default; 186 | $dark-lime-100: #cbf6b1 !default; 187 | $dark-lime-200: #aee58c !default; 188 | $dark-lime-300: #91d465 !default; 189 | $dark-lime-400: #7cc743 !default; 190 | $dark-lime-500: #65b909 !default; 191 | $dark-lime-600: #4fa200 !default; 192 | $dark-lime-700: #378b00 !default; 193 | $dark-lime-800: #136d00 !default; 194 | $dark-lime-900: #005100 !default; 195 | $dark-green: (#d8ffd8, #bafaba, #96ea98, #6fda75, #4ece5a, #1ac23a, #00aa19, #009200, #007400, #005700) !default; 196 | $dark-green-50: #d8ffd8 !default; 197 | $dark-green-100: #bafaba !default; 198 | $dark-green-200: #96ea98 !default; 199 | $dark-green-300: #6fda75 !default; 200 | $dark-green-400: #4ece5a !default; 201 | $dark-green-500: #1ac23a !default; 202 | $dark-green-600: #00aa19 !default; 203 | $dark-green-700: #009200 !default; 204 | $dark-green-800: #007400 !default; 205 | $dark-green-900: #005700 !default; 206 | $dark-teal: (#c7fffe, #a7f5f0, #86e7e2, #5fd8d4, #26cac7, #00b5b2, #009997, #007f7c, #006463, #004c4b) !default; 207 | $dark-teal-50: #c7fffe !default; 208 | $dark-teal-100: #a7f5f0 !default; 209 | $dark-teal-200: #86e7e2 !default; 210 | $dark-teal-300: #5fd8d4 !default; 211 | $dark-teal-400: #26cac7 !default; 212 | $dark-teal-500: #00b5b2 !default; 213 | $dark-teal-600: #009997 !default; 214 | $dark-teal-700: #007f7c !default; 215 | $dark-teal-800: #006463 !default; 216 | $dark-teal-900: #004c4b !default; 217 | $dark-cyan: (#d0ffff, #b1ffff, #8ffbff, #69f3ff, #2beaff, #00d8ff, #0be, #00a0db, #0085c5, #006baf) !default; 218 | $dark-cyan-50: #d0ffff !default; 219 | $dark-cyan-100: #b1ffff !default; 220 | $dark-cyan-200: #8ffbff !default; 221 | $dark-cyan-300: #69f3ff !default; 222 | $dark-cyan-400: #2beaff !default; 223 | $dark-cyan-500: #00d8ff !default; 224 | $dark-cyan-600: #0be !default; 225 | $dark-cyan-700: #00a0db !default; 226 | $dark-cyan-800: #0085c5 !default; 227 | $dark-cyan-900: #006baf !default; 228 | $dark-blue: (#d1faff, #afe3ff, #88c9ff, #60aeff, #3e9aff, #0586ff, #0072e9, #005ed4, #0043b9, #00279e) !default; 229 | $dark-blue-50: #d1faff !default; 230 | $dark-blue-100: #afe3ff !default; 231 | $dark-blue-200: #88c9ff !default; 232 | $dark-blue-300: #60aeff !default; 233 | $dark-blue-400: #3e9aff !default; 234 | $dark-blue-500: #0586ff !default; 235 | $dark-blue-600: #0072e9 !default; 236 | $dark-blue-700: #005ed4 !default; 237 | $dark-blue-800: #0043b9 !default; 238 | $dark-blue-900: #00279e !default; 239 | $dark-indigo: (#f3ebff, #dccbfd, #c3abf9, #ab8af4, #9468ef, #804fe0, #6f44c9, #6038b1, #512d9b, #432285) !default; 240 | $dark-indigo-50: #f3ebff !default; 241 | $dark-indigo-100: #dccbfd !default; 242 | $dark-indigo-200: #c3abf9 !default; 243 | $dark-indigo-300: #ab8af4 !default; 244 | $dark-indigo-400: #9468ef !default; 245 | $dark-indigo-500: #804fe0 !default; 246 | $dark-indigo-600: #6f44c9 !default; 247 | $dark-indigo-700: #6038b1 !default; 248 | $dark-indigo-800: #512d9b !default; 249 | $dark-indigo-900: #432285 !default; 250 | $dark-purple: (#ffe1ff, #f5c0ff, #e09aff, #ca71ff, #ba4ffc, #aa1af1, #9900de, #8900cc, #7301b4, #5e019c) !default; 251 | $dark-purple-50: #ffe1ff !default; 252 | $dark-purple-100: #f5c0ff !default; 253 | $dark-purple-200: #e09aff !default; 254 | $dark-purple-300: #ca71ff !default; 255 | $dark-purple-400: #ba4ffc !default; 256 | $dark-purple-500: #aa1af1 !default; 257 | $dark-purple-600: #9900de !default; 258 | $dark-purple-700: #8900cc !default; 259 | $dark-purple-800: #7301b4 !default; 260 | $dark-purple-900: #5e019c !default; 261 | $dark-magenta: (#ffe2fe, #ffc5e9, #ffa4d3, #ff82be, #ff67af, #f6489f, #de2d8a, #c40076, #a4005d, #850043) !default; 262 | $dark-magenta-50: #ffe2fe !default; 263 | $dark-magenta-100: #ffc5e9 !default; 264 | $dark-magenta-200: #ffa4d3 !default; 265 | $dark-magenta-300: #ff82be !default; 266 | $dark-magenta-400: #ff67af !default; 267 | $dark-magenta-500: #f6489f !default; 268 | $dark-magenta-600: #de2d8a !default; 269 | $dark-magenta-700: #c40076 !default; 270 | $dark-magenta-800: #a4005d !default; 271 | $dark-magenta-900: #850043 !default; 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | ## Command Line Options 2 | 3 | `Usage: chromatic file [options]` 4 | 5 | - `file` is a path to single token file, a directory, or a glob expression. If 6 | it is a path to a directory, all the `.yaml` files in this directory will be 7 | processed. 8 | - `--tokenFileExt` = `yaml`: when processing a directory, the file extension of 9 | the token files. 10 | - `--out`, `-o` \[optional\]: path to a directory or file where the output of 11 | the build process will be saved. If omitted, the result is output to the 12 | console. 13 | - `--verbose` = `false`: output progress information and warnings during 14 | processing. 15 | - `--validate` = `false`: process the token files, but do not output them. Use 16 | with `--verbose` to show additional warnings. 17 | - `--theme` \[optional\]: a comma separated list of the themes to output. If 18 | none is provided, all the themes are output. 19 | - `--format`, `-f` = ``: output format: `css|sass|js|yaml|json` 20 | - `--no-color`: suppress color output in terminal 21 | 22 | ## Config options 23 | 24 | Some config options for Chromatic can be provided either: 25 | 26 | - In the `package.json` file under a `chromatic` property 27 | - In a `.chromaticrc` file (in JSON or YAML format) 28 | - In a file `.chromatic.json.,`.chromatic.yaml., 29 | `.chromatic.yml., or`.chromatic.js` file 30 | - In a `chromatic.config.js` CommonJS module 31 | 32 | The configuration file can be placed in the root of the project directory, or in 33 | any parent directory up to the home directory. 34 | 35 | The configuration file can include the following options: 36 | 37 | - `theme`: an array of the themes to consider when generating tokens 38 | - `defaultTheme`: if no theme is specified, assume the token refers to this 39 | theme 40 | - `tokenFileExt` = `"yaml"`: When processing a directory, the file extension of 41 | the token files. 42 | - `valueFormatters`: a dictionary of formating functions. Each function is 43 | passed a token value and type and should return a formated string. 44 | 45 | ```javascript 46 | { 47 | valueFormatters: { 48 | "uppercase": (value, _type) => value.toString().toUpperCase(), 49 | } 50 | } 51 | ``` 52 | 53 | - `nameFormatters`: a dictionary of property name formating functions. Each 54 | function is passed a token name, type and theme and should return a formated 55 | property name. 56 | 57 | ```javascript 58 | { 59 | nameFormatters: { 60 | "uppercase-with-theme": (token, _type, theme) => (token + '-' + theme).toUpperCase(), 61 | } 62 | } 63 | ``` 64 | 65 | - `formats`: a dictionary of output file format. The key is the name of the 66 | format and the value is an object literal describing it. See "Output File 67 | Format" 68 | 69 | ## The Token File Format 70 | 71 | A token file is a YAML or JSON file that contains a platform agnostic definition 72 | of the design tokens. YAML is recommended over JSON because it is easier to 73 | include multi-line comments in this format. 74 | 75 | The file can include the following keys: 76 | 77 | - `tokens`: a map of token name to token definition. A token definition can be 78 | as simple a string, or an object literal with the following keys: 79 | - `value`: the value of this token, expressed either as a string, or as a map, 80 | with each key representing the name of a theme, and the value corresponding 81 | to the value of this token for the theme. 82 | - `type` 83 | - `comment` A short, one-line comment 84 | - `remarks` A longer, multi-line, explanation as a Markdown string. 85 | - `category` A grouping used when generating documentation 86 | - `contrast-backround` For type "`color`", a color (or alias to a color) that 87 | will be used as the background color for this token. This is used to 88 | calculate color contrast and issue a warning if the contrast falls below the 89 | W3C recommendations. 90 | - `import`: either a string or an array of strings, each a path to another token 91 | file that will be included before processing this one. Note that token files 92 | are only processed once. If the string is a directory, all the `.yaml` files 93 | in it will be processed. The path can be either an absolute file path, a file 94 | path relative to the token file, or a node module path (i.e. just the name of 95 | the module) 96 | - `theme`: any value for the tokens in this specified will be assumed to apply 97 | to the specified theme. This is useful to group in a single file all the token 98 | values for a specific theme 99 | 100 | ### Type 101 | 102 | | Type | Example | 103 | | ------------ | ----------------- | 104 | | `percentage` | `50%` | 105 | | `angle` | `230deg`, `50rad` | 106 | | `color` | `salmon`, `#ff0` | 107 | | `length` | `12px`, `5em` | 108 | | `number` | `12.55` | 109 | | `string` | `"Helvetica"` | 110 | | `time` | `200ms` | 111 | | `frequency` | `10hz` | 112 | 113 | Some types are expressed in specific units: 114 | 115 | - `length`: `px`, `cm`, `mm`, `Q`, `in`, `pc`, `pt`, `em`, `ex`, `ch`, `rem`, 116 | `vw`, `vh`, `vmin`, `vmax` 117 | - `angle`: `deg`, `grad`, `rad`, `degree`, `turn` 118 | - `time`: `s`, `ms` 119 | - `frequency`, `hz`, `khz` 120 | 121 | ### Expressive Design Tokens 122 | 123 | The value of a token is defined by an expression. The expression can include 124 | math operators, aliases refering to other tokens and functions 125 | 126 | | Function | Description | 127 | | ------------ | -------------------- | 128 | | calc() | | 129 | | rgb() | Return a color value | 130 | | rgba() | Synonym for rgb() | 131 | | hsl() | Return a color value | 132 | | hsla() | Synonym for hsl() | 133 | | hsv() | | 134 | | hwb() | | 135 | | lab() | | 136 | | gray() | | 137 | | min() | | 138 | | max() | | 139 | | clamp() | | 140 | | mix() | | 141 | | saturate() | | 142 | | desaturate() | | 143 | | lighten() | | 144 | | darken() | | 145 | | rotateHue() | | 146 | | complement() | | 147 | | contrast() | | 148 | | tint() | | 149 | | shade() | | 150 | 151 | See [Functions Reference](functions.md) for more details on each function. 152 | 153 | Token value expressions can refer to other tokens. A token does not have to be 154 | declared separately to be used as an "alias", and it does not have to be 155 | declared before being used. A token alias is indicated by enclosing the token 156 | name in '{' and '}'. 157 | 158 | ```yaml 159 | tokens: 160 | primary: '#0066ce' 161 | cta-button-background: '{primary}' 162 | ``` 163 | 164 | Color scale variants of a color can also be referenced without having to 165 | explicitly define the variants in advance: 166 | 167 | ```yaml 168 | tokens: 169 | primary: '#0066ce' 170 | cta-button-active-background: '{primary-200}' 171 | ``` 172 | 173 | This is equivalent to : 174 | 175 | ```yaml 176 | tokens: 177 | primary: '#0066ce' 178 | cta-button-active-background: 'scale({primary})[2]' 179 | ``` 180 | 181 | or: 182 | 183 | ```yaml 184 | tokens: 185 | primary: '#0066ce' 186 | primary-200: 'scale({primary})[2]' 187 | cta-button-active-background: '{primary-200}' 188 | ``` 189 | 190 | It is of course possible to override the default value if desired. 191 | 192 | ### Category 193 | 194 | Each token can be associated with a "category". 195 | 196 | ```json 197 | { 198 | tokens: { 199 | hero-color: 200 | value: "#333", 201 | category: "text-color" 202 | } 203 | } 204 | ``` 205 | 206 | Standard categories: 207 | 208 | | Category | Name | 209 | | --------------------- | ---------------------- | 210 | | `spacing` | Spacing | 211 | | `sizing` | Sizing | 212 | | `font` | Fonts | 213 | | `font-style` | Font Styles | 214 | | `font-weight` | Font Weights | 215 | | `font-size` | Font Sizes | 216 | | `line-height` | Line Heights | 217 | | `font-family` | Font Families | 218 | | `border-style` | Border Styles | 219 | | `border-color` | Border Colors | 220 | | `radius` | Radius | 221 | | `border-radius` | Border Radii | 222 | | `hr-color` | Horizontal Rule Colors | 223 | | `background-color` | Background Colors | 224 | | `gradient` | Gradients | 225 | | `background-gradient` | Background Gradients | 226 | | `drop-shadow` | Drop Shadows | 227 | | `box-shadow` | Box Shadows | 228 | | `inner-shadow` | Inner Drop Shadows | 229 | | `text-color` | Text Colors | 230 | | `text-shadow` | Text Shadows | 231 | | `time` | Time | 232 | | `media-query` | Media Queries | 233 | | `z-index` | Z-Index | 234 | 235 | ## Examples 236 | 237 | ### Basic token file 238 | 239 | ```yaml 240 | tokens: 241 | red: '#FF0000' 242 | blue: '#0ff00' 243 | green: '#0000FF' 244 | white: '#fff' 245 | ``` 246 | 247 | The same tokens, expressed in JSON: 248 | 249 | ```json 250 | { 251 | "tokens": { 252 | "red": "#FF0000", 253 | "blue": "#0ff00", 254 | "green": "#0000FF", 255 | "white": "#fff" 256 | } 257 | } 258 | ``` 259 | 260 | ### Token file with themes 261 | 262 | ```yaml 263 | tokens: 264 | background: 265 | value: 266 | dark: '#333' 267 | light: '#CCC' 268 | text-color: 269 | value: 270 | dark: '#ddd' 271 | light: '#222' 272 | ``` 273 | 274 | ## Output File Format 275 | 276 | The `formats` property of the config object is a key/value map of format names 277 | and format description. The format name is used as an argument to the `--format` 278 | option in the command line or the `format` option in the config file. 279 | 280 | A format description may include the following properties: 281 | 282 | - `extends`: The name of another format this one is based on. It will inherit 283 | all the properties from this format, and can override the necessary ones. 284 | - `ext`: The file extension for the output file, for example `".css"` 285 | - `fileHeader`: A message to include at the top of the output file. This could 286 | be a copyright notice, or a warning that the file is auto-generated and should 287 | not be modified manually. 288 | - `valueFormatters`: An array of strings, the name of the value formatters to 289 | apply in sequence on token values before outputing them. As a shorthand, this 290 | can be a string, or a function. 291 | - `nameFormatters`: An array of strings, the name of the name formatters to 292 | apply in sequence on the token name before outputing it. As a shorthand, this 293 | can be a string or a function. 294 | - `propertyTemplate`: A function that returns a property formated for output. 295 | Its input parameters are the formatted name of the token, the formatted value 296 | of the token and the token entry, which includes the type the token and the 297 | comment associated with it. 298 | - `filenameTemplate`: A function that returns the name of the file to write to, 299 | given a base filename and a theme name. If the same value is returned for 300 | various 'theme' values, the properties will be output to the same file. 301 | - `groupTemplate`: A function that returns as a string the content of a group of 302 | related properties, for example properties belonging to the same them or the 303 | same category. Its input parameter is an object literal with the following 304 | keys: 305 | - `header` 306 | - `properties`: an array of string, one for each property 307 | - `filename`: the name of the output file 308 | - `filepath`: the full path of the output file 309 | - `theme`: if the properties are grouped by theme, the theme they share 310 | - `category`: if the properties are grouped by category, the category they 311 | share 312 | - `fileTemplate`: A function that returns as a string the output file. Its input 313 | parameter is an object literal with the following keys: 314 | - `header` 315 | - `content`: a string representing the concatenated groups 316 | - `filename`: the name of the output file 317 | - `filepath`: the full path of the output file 318 | 319 | ### Standard Value Formatters 320 | 321 | | Name | Description | 322 | | --------------------- | ----------------------------------------------------------------- | 323 | | `color/rgb` | Convert to rgb | 324 | | `color/hex` | Convert to hex | 325 | | `color/hex8rgba` | Convert to hex8rgba | 326 | | `color/hex8argb` | Convert to hex8argb | 327 | | `percentage/float` | Convert a percentage to a decimal percentage | 328 | | `relative/pixel` | Convert a r/em value to a pixel value | 329 | | `relative/pixelValue` | Convert a r/em value to a pixel value (excluding the `px` suffix) | 330 | --------------------------------------------------------------------------------