You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When we started with the CLI we didn't have experience or much of a plan on how it would be used. This resulted in a patchwork of features, implemented as the need came up, which is cumbersome to use and not very thought out.
Users need to look up what command to run or forget to run the latter command for CSS file.
tokens create uses designsystemet.config.json for creating tokens with fields like outDir and clean tied only to create.
tokens builddoes not usedesignsystemet.config.json, instead you need to pass terminal arguments such as --out-dir, --clean, --tokens to configure how it runs.
no option for turning off types.d.ts generation, or in general confusing configuration
continuing in the pattern of tokens create and tokens build is not very scalable, as we can generate CSS without design-tokens.
New way to run CLI
Declarative approach with everything that needs to be run defined in a config schema, designsystemet.config.json.
New output field which lets users decide which output they want to create/generate for designsystemet. Just executing the bin/command, designsystemet, creates everything defined in output, so users can pick and choose which outputs they want created for their designsystem.
This is inspired with how CLI for bundlers, tsdown, rolldown, rollup etc. so should be more familiar.
This feature is also part of stabilising the config schema so we can remove the experimental we have today under config readme and page.
tokens create and tokens build still work, but are deprecated. outDir and clean are deprecated in favour of output, and the CLI offers to migrate existing config files automatically.
With no output field, you get the defaults, ["design-tokens", "css", "types"] once the whole stack is merged (["design-tokens", "css"] in this PR alone, as types arrives in #5444).
This is primarily to match today's expected output when running tokens create and tokens build. I expect us to adjust what default output will be in the future.
The config file is auto-detected (designsystemet.config.json, then designsystemet.config.jsonc), or passed with -c, --config <path>. All paths in output are relative to the config file.
How output works
output is a list of what to create. Each item is either an output type using its defaults, or an object with custom settings:
{
"output": [
"design-tokens", // output type with default values// object with configured output options for css file
{
"type": "css",
"dir": "./css",
"tokensDir": "./design-tokens",
"tailwind": "v4"
},
]
}
Cleaning is on by default:cleanDir defaults to true, so files that are no longer generated are removed. This was opt-in before (clean: false). Every output directory is cleaned once, before any output runs, so outputs can share a directory without deleting each other's files. The CLI refuses to clean a directory that contains the config file, existing design tokens that css or types build from (tokensDir), or another output with cleanDir: false, and stops before deleting anything.
Order:design-tokens always runs first, whatever order the outputs are listed in, since CSS and types can be built from the tokens.
tokensDir:css and types build from tokensDir, or else from the dir of the design-tokens output. This is decided per output, so one css output can build from existing tokens while another is created from the themes.
Without design tokens:"output": ["css", "types"] with no tokensDir creates CSS and types directly from the themes, without writing any design tokens to disk.
Without themes:themes is only needed by outputs that are created from themes. A config with only a css output and a tokensDir builds CSS from existing design tokens and can leave themes out:
If an output needs themes and there are none, the CLI stops with an error saying so, before cleaning anything.
Dry run:--dry writes nothing. CSS and types that would be built from the design tokens created in the same run are created from the themes instead, so the dry run shows the real result.
Inject design-tokens
We know some users today have scripts to manipulate design-tokens before building. Usually adding colors or adjusting size scale. This is still possible with output by using two configs and running designsystemet twice with each config.
create-tokens.json
{
"output": ["design-tokens"],
"themes": {} // your theme configuration
}
build-tokens.json
{
// only output is needed as we build css from design-tokens"output": [{
"type": "css",
"tokensDir": "./design-tokens",
}]
}
The css output has a tailwind option that replaces --experimental-tailwind. By default (false) no Tailwind file is generated. Set it to the Tailwind version you use to also get a <theme>.tailwind.css:
"v4" uses @theme inline, so Tailwind utilities reference the --ds-* variables directly. data-color, data-color-scheme and data-size then also apply to utilities, at any depth in the DOM.
"v3" generates the same file as before. The deprecated tokens build --experimental-tailwind keeps generating v3.
Both versions also get --text-sm, --text-md and --text-lg mapped to the body font sizes in #5451. These were never generated because of a typo in the token name matching.
Type declarations (types.d.ts) now have their own types output instead of always being written with the CSS. These augment @digdir/designsystemet-types with the theme's color names.
Note
The css output no longer writes type declarations. If you set output yourself, add "types" to keep getting them. The default output, and configs migrated from outDir, already include it.
Migrating existing configs
When designsystemet finds outDir or clean, it offers to migrate the config file:
β Automigration detected
Config file designsystemet.config.json is eligible for migration: New output field
Your config file uses the deprecated outDir and clean fields.
This migration will replace them with a new output field if necessary.
? Do you want to migrate? (Y/n)
Default values: if outDir already has its default value and clean isn't false, the migration just removes the old fields and doesn't add an output.
clean: false: an explicit "clean": false is carried over as "cleanDir": false on every output that is cleaned, so folders the user opted out of cleaning are not deleted. This adds an output even when outDir has its default value.
Paths:outDir was resolved from the directory the CLI ran in, but output paths are resolved from the config file. When the config isn't in that directory, the migration rewrites outDir so it still points to the same place.
Formatting: the migration edits the file in place, so comments and formatting are kept. output is placed right after $schema.
Declining:designsystemet stops without cleaning or writing anything, since it only reads output. tokens create keeps using outDir and clean.
tokens create: doesn't offer this migration, since it only reads outDir and clean and would ignore the migrated output. It prints a hint to run designsystemet instead.
generate-config-from-tokens uses the same mapping, so a generated config gets the same output as a migrated one.
Deprecations
tokens create / tokens build: these now print a deprecation warning and are marked [deprecated] in --help. They're kept for backwards compatibility, keep using outDir and clean, and will be removed in a future release.
outDir / clean: marked deprecated in the Config schema, and replaced by output[].dir / output[].cleanDir.
experimental_tailwind: replaced by tailwind on the css output.
Other changes
generate-config-from-tokens: the generated config now uses output instead of the deprecated outDir, with the tokens directory written relative to the config file (--out) rather than as an absolute path. If the tokens are in the default design-tokens directory next to the config, no output is written and the defaults apply. It refuses an --out inside the tokens directory, since running that config would clean the directory it's in.
themes is optional:themes is no longer required by the config schema or the published JSON schema. An empty "themes": {} is still rejected.
Severity color defaults (chore: better handling of severity default color namesΒ #5443): the default info, success, warning and danger colors are now added during config validation, instead of in several places in the token generation. User-defined severity colors keep their value, and all severity colors are placed last. neutral is still required.
Root config: the repo's designsystemet.config.json sets "tailwind": "v3" on its css output, so running the new command keeps the published packages/css/theme/designsystemet.tailwind.css identical to what build:theme generates.
Theme builder: the "use theme" modal now shows npx @digdir/designsystemet as the build command. The config snippet no longer includes outDir, the hard-coded typography, or borderRadius when it's the default.
Removed banner: the banner option on the css output was never used by the CLI, so it's gone from the schema.
Docs: the cli-config, own-theme, multiple-themes and css pages (en and no), the CLI README and the CSS README are updated to use the new command, output and the tailwind option.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
π΅ Needs a closer look
Declining or skipping migration causes legacy output paths and cleaning settings to be silently ignored.
Review effort: Balanced Findings: None
Previously missed (1)
In code that hasn't changed since last review
Correct release note to reference output directories
.changeset/βsour-turtles-begin.md:5
This release note refers to deprecated outDir folders, but the new API cleans each output[].dir; saying outDir implies the legacy field controls the behavior.
A JSONC comment between a deprecated property's value and its comma is not whitespace, so this regex misses the comma. Removing { "outDir": "tokens" /* note */, "themes": {} } then leaves { /* note */, "themes": {} }, which is invalid and makes the automatic migration corrupt an otherwise valid config. Make removal comment-aware and cover inline block/line comments around the comma.
Update docblock to include the public output property
This docblock still says the public schema excludes output, but the changed declaration now extends the complete config schema and intentionally publishes output. Update the comment to reflect the new public contract.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
π’ Approval recommended
The implementation is coherent and well-tested; remaining findings are minor documentation corrections.
Review effort: Balanced Findings: None
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
resolves #5191
Summary
Current way to run CLI
When we started with the CLI we didn't have experience or much of a plan on how it would be used. This resulted in a patchwork of features, implemented as the need came up, which is cumbersome to use and not very thought out.
designsystemet tokens create && designsystemet tokens build -t ./design-tokens -o ./design-tokens-buildtokens createusesdesignsystemet.config.jsonfor creating tokens with fields likeoutDirandcleantied only to create.tokens builddoes not usedesignsystemet.config.json, instead you need to pass terminal arguments such as--out-dir,--clean,--tokensto configure how it runs.types.d.tsgeneration, or in general confusing configurationtokens createandtokens buildis not very scalable, as we can generate CSS without design-tokens.New way to run CLI
Declarative approach with everything that needs to be run defined in a config schema,
designsystemet.config.json.New
outputfield which lets users decide which output they want to create/generate for designsystemet. Just executing the bin/command,designsystemet, creates everything defined inoutput, so users can pick and choose which outputs they want created for their designsystem.This is inspired with how CLI for bundlers,
tsdown,rolldown,rollupetc. so should be more familiar.This feature is also part of stabilising the config schema so we can remove the experimental we have today under config readme and page.
tokens createandtokens buildstill work, but are deprecated.outDirandcleanare deprecated in favour ofoutput, and the CLI offers to migrate existing config files automatically.Stack
outputfield and root command (this PR)tailwindoption on thecssoutput, with Tailwind v3 and v4 supporttypesoutput for TypeScript declarations--text-*variables in the Tailwind theme filePreview
Before
{ "outDir": "./design-tokens", // only used by `tokens create` "clean": true, "themes": { "my-theme": { "colors": { "accent": "#0062BA", "neutral": "#1E2B3C" } } } }After
{ "themes": { "my-theme": { "colors": { "accent": "#0062BA", "neutral": "#1E2B3C" } } } }With no
outputfield, you get the defaults,["design-tokens", "css", "types"]once the whole stack is merged (["design-tokens", "css"]in this PR alone, astypesarrives in #5444).This is primarily to match today's expected output when running
tokens createandtokens build. I expect us to adjust what default output will be in the future.The config file is auto-detected (
designsystemet.config.json, thendesignsystemet.config.jsonc), or passed with-c, --config <path>. All paths inoutputare relative to the config file.How
outputworksoutputis a list of what to create. Each item is either an output type using its defaults, or an object with custom settings:{ "output": [ "design-tokens", // output type with default values // object with configured output options for css file { "type": "css", "dir": "./css", "tokensDir": "./design-tokens", "tailwind": "v4" }, ] }Cleaning is on by default:
cleanDirdefaults totrue, so files that are no longer generated are removed. This was opt-in before (clean: false). Every output directory is cleaned once, before any output runs, so outputs can share a directory without deleting each other's files. The CLI refuses to clean a directory that contains the config file, existing design tokens thatcssortypesbuild from (tokensDir), or another output withcleanDir: false, and stops before deleting anything.Order:
design-tokensalways runs first, whatever order the outputs are listed in, since CSS and types can be built from the tokens.tokensDir:cssandtypesbuild fromtokensDir, or else from thedirof thedesign-tokensoutput. This is decided per output, so onecssoutput can build from existing tokens while another is created from the themes.Without design tokens:
"output": ["css", "types"]with notokensDircreates CSS and types directly from the themes, without writing any design tokens to disk.Without themes:
themesis only needed by outputs that are created from themes. A config with only acssoutput and atokensDirbuilds CSS from existing design tokens and can leavethemesout:{ "output": [{ "type": "css", "tokensDir": "./design-tokens" }] }If an output needs themes and there are none, the CLI stops with an error saying so, before cleaning anything.
--drywrites nothing. CSS and types that would be built from the design tokens created in the same run are created from the themes instead, so the dry run shows the real result.Inject design-tokens
We know some users today have scripts to manipulate design-tokens before building. Usually adding colors or adjusting size scale. This is still possible with
outputby using two configs and runningdesignsystemettwice with each config.create-tokens.json{ "output": ["design-tokens"], "themes": {} // your theme configuration }build-tokens.json{ // only output is needed as we build css from design-tokens "output": [{ "type": "css", "tokensDir": "./design-tokens", }] }run
Tailwind (#5438)
The
cssoutput has atailwindoption that replaces--experimental-tailwind. By default (false) no Tailwind file is generated. Set it to the Tailwind version you use to also get a<theme>.tailwind.css:"v4"uses@theme inline, so Tailwind utilities reference the--ds-*variables directly.data-color,data-color-schemeanddata-sizethen also apply to utilities, at any depth in the DOM."v3"generates the same file as before. The deprecatedtokens build --experimental-tailwindkeeps generating v3.Both versions also get
--text-sm,--text-mdand--text-lgmapped to the body font sizes in #5451. These were never generated because of a typo in the token name matching.Types (#5444)
Type declarations (
types.d.ts) now have their owntypesoutput instead of always being written with the CSS. These augment@digdir/designsystemet-typeswith the theme's color names.Note
The
cssoutput no longer writes type declarations. If you setoutputyourself, add"types"to keep getting them. The defaultoutput, and configs migrated fromoutDir, already include it.Migrating existing configs
When
designsystemetfindsoutDirorclean, it offers to migrate the config file:"outDir": "./tokens", "clean": truebecomes:{ "output": [ { "type": "design-tokens", "dir": "./tokens" }, { "type": "css", "tokensDir": "./tokens" }, { "type": "types", "tokensDir": "./tokens" } ] }outDiralready has its default value andcleanisn'tfalse, the migration just removes the old fields and doesn't add anoutput.clean: false: an explicit"clean": falseis carried over as"cleanDir": falseon every output that is cleaned, so folders the user opted out of cleaning are not deleted. This adds anoutputeven whenoutDirhas its default value.outDirwas resolved from the directory the CLI ran in, butoutputpaths are resolved from the config file. When the config isn't in that directory, the migration rewritesoutDirso it still points to the same place.outputis placed right after$schema.designsystemetstops without cleaning or writing anything, since it only readsoutput.tokens createkeeps usingoutDirandclean.tokens create: doesn't offer this migration, since it only readsoutDirandcleanand would ignore the migratedoutput. It prints a hint to rundesignsystemetinstead.generate-config-from-tokensuses the same mapping, so a generated config gets the sameoutputas a migrated one.Deprecations
tokens create/tokens build: these now print a deprecation warning and are marked[deprecated]in--help. They're kept for backwards compatibility, keep usingoutDirandclean, and will be removed in a future release.outDir/clean: markeddeprecatedin the Config schema, and replaced byoutput[].dir/output[].cleanDir.experimental_tailwind: replaced bytailwindon thecssoutput.Other changes
generate-config-from-tokens: the generated config now usesoutputinstead of the deprecatedoutDir, with the tokens directory written relative to the config file (--out) rather than as an absolute path. If the tokens are in the defaultdesign-tokensdirectory next to the config, nooutputis written and the defaults apply. It refuses an--outinside the tokens directory, since running that config would clean the directory it's in.themesis optional:themesis no longer required by the config schema or the published JSON schema. An empty"themes": {}is still rejected.info,success,warninganddangercolors are now added during config validation, instead of in several places in the token generation. User-defined severity colors keep their value, and all severity colors are placed last.neutralis still required.designsystemet.config.jsonsets"tailwind": "v3"on itscssoutput, so running the new command keeps the publishedpackages/css/theme/designsystemet.tailwind.cssidentical to whatbuild:themegenerates.npx @digdir/designsystemetas the build command. The config snippet no longer includesoutDir, the hard-codedtypography, orborderRadiuswhen it's the default.banner: thebanneroption on thecssoutput was never used by the CLI, so it's gone from the schema.cli-config,own-theme,multiple-themesandcsspages (en and no), the CLI README and the CSS README are updated to use the new command,outputand thetailwindoption.