Skip to content

Latest commit

 

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cssmark

CSS @property as the single source of truth for design token authoring, documentation, and distribution.

A toolchain that treats @property blocks as the canonical token definition format. Extended descriptors carry metadata that browsers ignore but the tool consumes.

Zero dependencies. Single binary. Written in Go.

Installation

npm (Recommended for Node.js projects)

npm install @artmsilva/cssmark
npx cssmark build tokens.css -o tokens.json

Add to your package.json scripts:

{
  "scripts": {
    "tokens:build": "cssmark build src/tokens.css -o dist/tokens.json",
    "tokens:docs": "cssmark docs src/tokens.css -o dist/docs",
    "tokens:validate": "cssmark validate src/tokens.css"
  }
}

Download Binary

Download from releases.

Build from Source

git clone https://github.com/artmsilva/cssmark
cd cssmark
go build -o cssmark ./src/cmd/cssmark

Go Install

go install github.com/artmsilva/cssmark/src/cmd/cssmark@latest

Token Authoring Format

Tokens are authored as standard @property rules with additional descriptors for metadata. Keep source files small and compose them through a flat index.css:

/* tokens/index.css */
@import "./color.css";
@import "./typography.css";

/* tokens/color.css */
@property --color-brand-primary {
  /* Standard @property descriptors — browser-native */
  syntax: "<color>";
  inherits: false;
  initial-value: #0055ff;

  /* Extended descriptors — tool-only */
  description: "Primary brand color for interactive elements.";
  category: "color.brand";
  type: "color";
  aliases: "--color-primary, --color-action";
  deprecated: false;
  examples: "background: var(--color-brand-primary); border-color: var(--color-brand-primary);";
}

Composite tokens: scalar axes, not JSON

CSS has no object-valued @property, so typography and transition tokens are authored as scalar axes. composite links related axes without making the source JSON-shaped.

@property --hb-decorative-type-body-regular-font-family {
  id: "decorative.type.body.regular.fontFamily";
  composite: "decorative.type.body.regular";
  syntax: "*";
  inherits: true;
  initial-value: var(--hb-font-family-body);
}

@property --hb-decorative-type-body-regular-font-size {
  id: "decorative.type.body.regular.fontSize";
  composite: "decorative.type.body.regular";
  syntax: "<length>";
  inherits: true;
  initial-value: var(--hb-space-4);
  mode-dense: var(--hb-space-3-5);
}

Typography uses font-family, font-size, font-style, font-weight, and line-height axes. Transition uses duration, delay, and timing-function. Override only the axis that changes in a mode.

The compiler rebuilds the legacy font shorthand automatically from a complete typography composite, using axis-variable references. So mode-dense on only font-size still changes both the axis and the compatible shorthand token. The DTCG migrator omits duplicate typography-shorthand source tokens because their regular typography sibling now owns the same runtime shorthand.

The migration source also deduplicates typography variants against a local regular sibling:

@token regular {
  font-family: var(--hb-font-family-body);
  font-size: var(--hb-space-4);
  font-style: normal;
  font-weight: var(--hb-font-weight-regular);
  line-height: var(--hb-space-6);
}

@token bold {
  extends: regular;
  font-weight: var(--hb-font-weight-bold);
}

extends inherits base axes and mode values; a child only declares its differences. The migration also replaces explicit generated dense overrides with policy declarations:

@derive dense {
  font-size: step-down(1, floor: var(--hb-space-3));
  line-height: step-down(1, floor: var(--hb-space-4));
}

@token, extends, and @derive are cssmark source syntax. The current migrator emits them; parser/lowering support is the required next cutover step before this source can replace DTCG in a build.

var() References

Tokens can reference other tokens using var(). References are resolved to literal values in JSON output, while CSS output preserves the original var() references.

@property --color-blue-500 {
  syntax: "<color>";
  inherits: false;
  initial-value: #0055ff;
}

@property --color-primary {
  syntax: "<color>";
  inherits: false;
  initial-value: var(--color-blue-500);
}

JSON output resolves the chain:

{ "name": "--color-primary", "initialValue": "#0055ff" }

CSS output preserves the reference:

:root {
  --color-blue-500: #0055ff;
  --color-primary: var(--color-blue-500);
}

Chained references (var(--a)var(--b)#fff) are fully resolved. Circular and unknown references are kept as-is.

Mode Descriptors

Define alternative values for different modes using mode-* descriptors. These generate :root[data-color-mode='...'] override blocks in CSS output.

@property --color-bg {
  syntax: "<color>";
  inherits: false;
  initial-value: #ffffff;
  mode-dark: #1a1a2e;
  mode-high-contrast: #000000;
}

CSS output:

:root {
  --color-bg: #ffffff;
}

:root[data-color-mode='dark'] {
  --color-bg: #1a1a2e;
}

:root[data-color-mode='high-contrast'] {
  --color-bg: #000000;
}

JSON output includes modes as a map:

{ "name": "--color-bg", "initialValue": "#ffffff", "modes": { "dark": "#1a1a2e", "high-contrast": "#000000" } }

Mode values also support var() references, which are resolved the same way.

Use --mode-selector mode=selector when a mode is not a color mode. For example, dense spacing can target both the document root and nested containers:

cssmark css tokens.css --out tokens.css \
  --mode-selector "dense=:root[data-density='dense'], [data-density='dense']"

Commands

Build (JSON Export)

cssmark build tokens.css --out tokens.json

Import DTCG JSON for migration

dtcg is a migration aid for existing DTCG token trees. It recursively flattens JSON files, inherits group $type, merges later mode-only overlays by token path, and preserves stable IDs, CSS names, values, and modes in a JSON manifest.

cssmark dtcg context palettes --prefix hb --out token-migration.json

Add --tree-out tokens to produce a flat, composable cssmark authoring tree. Every file lives directly under tokens/, grouped by DTCG type—not consumer-oriented semantic domains—such as color.css, dimension.css, typography.css, and transition.css. Raw DTCG wire types are normalized where CSS has a clearer authoring name: cubicBeziertiming-function.css, fontFamilyfont-family.css, and fontWeightfont-weight.css. index.css contains only the ordered imports. It does not emit runtime :root or mode override files.

Within each type file, semantic paths are nested. This keeps state modifiers together with their base concern:

@token color {
  @token action {
    @token border {
      @token primary {
        @token default { value: var(--hb-color-brand-blue-450); }
        @token hover { value: var(--hb-color-brand-blue-600); }
        @token disabled { value: var(--hb-color-brand-gray-500); }
      }
    }
  }
}

The compiler flattens this to the existing CSS variable names (--hb-color-action-border-primary-default, etc.).

Composite tokens avoid JSON authoring: typography becomes five scalar @property blocks (font-family, font-size, font-style, font-weight, and line-height) and transitions become scalar duration, delay, and timing-function blocks. A composite descriptor associates those axes so cssmark can later re-create existing shorthand output. Partial mode overrides stay on the affected axis only.

--css-out tokens.source.css remains available for a single-file authoring artifact.

Generate JavaScript artifacts

Use id to preserve a stable programmatic key when the CSS variable name is not enough to reconstruct it.

@property --hb-color-action-primary {
  id: "color.action.primary";
  syntax: "<color>";
  inherits: false;
  initial-value: #0055ff;
  mode-dark: #66aaff;
}
cssmark js tokens.css \
  --out tokens.js \
  --meta-out tokens.meta.js \
  --dts-out tokens.d.ts

This emits an ESM tokens map, per-token modes, a token(id, mode) helper, metadata, and a declaration file.

Generate Documentation

cssmark docs tokens.css --out ./docs

Validate Tokens

cssmark validate tokens.css

Diff Token Snapshots

cssmark diff tokens.old.json tokens.new.json

Extended Descriptors

Descriptor Type Required Description
description string no Human-readable explanation of the token
category string no Dot-separated group path: color.brand
type string no Semantic hint: color, size, duration
aliases string no Comma-separated list of related props
deprecated boolean no Marks token deprecated
examples string no Semicolon-separated CSS usage examples
mode-* string no Override value for a named mode
id string no Stable programmatic token identity
composite string no Shared identity for scalar composite axes

Output Formats

JSON

[
  {
    "name": "--color-brand-primary",
    "syntax": "<color>",
    "inherits": false,
    "initialValue": "#0055ff",
    "modes": {
      "dark": "#66aaff"
    },
    "description": "Primary brand color for interactive elements.",
    "category": "color.brand",
    "type": "color",
    "aliases": ["--color-primary", "--color-action"],
    "deprecated": false,
    "examples": ["background: var(--color-brand-primary);"]
  }
]

When a token uses var() references, initialValue contains the resolved literal value. The modes field is omitted when no mode-* descriptors are defined.

Static Documentation Site

A minimal, fast reference site with:

  • Sidebar with category tree
  • Token cards grouped by category
  • Color swatches, type badges, examples
  • Deprecated token warnings
  • Mobile drawer navigation
  • Optional source stylesheet viewer with tiny built-in syntax highlighting

The project site and generated example docs are separate:

Trust Model and Current Parser Scope

cssmark is a build-time tool for trusted source files in your repository. The generated docs inline token values into preview styles and display the source stylesheet, so do not generate public docs from secrets or untrusted CSS.

The parser intentionally targets cssmark's authoring subset: standard @property blocks plus cssmark extended descriptors. It is not a full CSS parser yet. Complex CSS outside token definitions may be ignored, but cross-file var() references between parsed tokens are resolved after all input files are loaded.

License

MIT

About

CSS @Property as the single source of truth for design token authoring, documentation, and distribution.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages