Lint

The idea of token linting is similar to any kind of code linting—you can run checks on your tokens to raise errors and warnings based on a number of criteria:

RuleDescription
core/valid-colorRequire color tokens to follow the format.
core/valid-dimensionRequire dimension tokens to follow the format.
core/valid-font-familyRequire fontFamily tokens to follow the format.
core/valid-font-weightRequire fontWeight tokens to follow the format.
core/valid-durationRequire duration tokens to follow the format.
core/valid-cubic-bezierRequire cubicBezier tokens to follow the format.
core/valid-numberRequire number tokens to follow the Terrazzo extension.
core/valid-linkRequire link tokens to follow the Terrazzo extension.
core/valid-booleanRequire boolean tokens to follow Terrazzo extension.
core/valid-stringRequire string tokens to follow the Terrazzo extension.
core/valid-stroke-styleRequire stroke-style tokens to follow the format.
core/valid-borderRequire border tokens to follow the format.
core/valid-transitionRequire transition tokens to follow the format.
core/valid-shadowRequire shadow tokens to follow the format.
core/valid-gradientRequire gradient tokens to follow the format.
core/valid-typographyRequire typography tokens to follow the format.
core/colorspaceEnforce that all colors are declared in a specific colorspace (e.g. sRGB).
core/consistent-namingEnforce a consistent naming style (e.g. camelCase).
core/duplicate-valueEnforce tokens can’t redeclare the same value (excludes aliases).
core/descriptionsEnforce tokens have descriptions.
core/max-gamutEnforce colors are within the specified gamut (e.g. display-p3).
core/required-childrenEnforce token groups have specific children, whether tokens and/or groups.
core/required-modesEnforce certain tokens have specific modes.
core/required-typography-propertiesEnforce typography tokens have required properties (e.g. lineHeight).
a11y/min-contrastEnsure minimum WCAG 2.2 contrast given token pairs.
a11y/min-font-sizeEnsure minimum font size.

Config

To opt into a lint rule, add a lint.rules object in your config. The key is the lint rule name, and the value is a string indicating the severity:

SeverityDescription
"error"Stop token parsing immediately and throw an error.
"warn"Don’t block token parsing, but print an error to the console.
"off"Disable this rule.

Lastly, many rules accept additional options. If passing options, use the [severity, options] tuple format:

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/colorspace": "warn",
      "core/consistent-naming": ["error", { format: "kebab-case" }],
      "core/duplicate-values": "off",
    },
  },
});

Rules

core/valid-color

Require all color tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-color": [
        "error",
        { legacyFormat: false, ignoreRanges: false },
      ],
    },
  },
});
OptionDescription
legacyFormatAllow declaration of legacy sRGB hex codes for $value (default: false).
ignoreRangesAllow colors to exceed CSS Color Module 4 ranges (default: false).

core/valid-dimension

Require all dimension tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-dimension": [
        "error",
        {
          legacyFormat: false,
          allowedUnits: ["px", "rem"],
        },
      ],
    },
  },
});
OptionDescription
legacyFormatAllow legacy string values ("12px") (default: false).
unknownUnitsAllow usage of any unit values (default: false).

core/valid-font-family

Require all fontFamily tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-font-family": "error",
    },
  },
});

core/valid-font-weight

Require all fontWeight tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-font-weight": "error",
    },
  },
});

core/valid-duration

Require all duration tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-duration": "error",
    },
  },
});

core/valid-cubic-bezier

Require all cubicBezier tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-cubic-bezier": "error",
    },
  },
});

core/valid-number

Require all number tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-number": "error",
    },
  },
});

Require all link tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-link": "error",
    },
  },
});

core/valid-boolean

Require all boolean tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-boolean": "error",
    },
  },
});

core/valid-string

Require all string tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-string": "error",
    },
  },
});

core/valid-stroke-style

Require all strokeStyle tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-stroke-style": "error",
    },
  },
});

core/valid-border

Require all border tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-border": "error",
    },
  },
});

core/valid-transition

Require all transition tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-transition": "error",
    },
  },
});

core/valid-shadow

Require all shadow tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-shadow": "error",
    },
  },
});

core/valid-gradient

Require all gradient tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-gradient": "error",
    },
  },
});

core/valid-typography

Require all typography tokens to follow the format.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/valid-typography": [
        "error",
        {
          requiredProperties: [
            "fontFamily",
            "fontSize",
            "fontWeight",
            "lineHeight",
            "letterSpacing",
          ],
        },
      ],
    },
  },
});

core/colorspace

Enforce that all colors are declared in a specific color space (e.g. sRGB).

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/colorspace": ["error", { colorSpace: "oklab" }],
    },
  },
});
OptionDescription
colorSpaceAny valid CSS Color Module 4 predefined color space, e.g. srgb, display-p3, oklab, etc.
ignoreArray of token globs to ignore, e.g. ('["legacy.*"])

core/consistent-naming

Enforce a consistent naming style (e.g. camelCase).

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/consistent-naming": ["error", { format: "kebab-case" }],
    },
  },
});
OptionDescription
formatkebab-case, camelCase, PascalCase, snake_case, SCREAMING_SNAKE_CASE, or a custom validator function.
ignoreArray of token globs to ignore, e.g. ('["legacy.*"])

core/duplicate-value

Enforce tokens can’t redeclare the same value (excludes aliases).

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/duplicate-value": "error",
    },
  },
});
OptionDescription
ignoreArray of token globs to ignore, e.g. ('["legacy.*"])

core/descriptions

Enforce tokens have descriptions. Having a description on a group doesn’t count.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/descriptions": "error",
    },
  },
});
OptionDescription
ignoreArray of token globs to ignore, e.g. '["legacy.*"]

core/max-gamut

Enforce colors are within the specified gamut.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/max-gamut": ["error", { format: "srgb" }],
    },
  },
});
OptionDescription
gamutsrgb, p3, or rec2020.
ignoreArray of token globs to ignore, e.g. '["legacy.*"]

core/required-children

Enforce token groups have specific children, whether tokens and/or groups.

A Match consists of a match glob to match against, along with either requiredTokens and/or requiredGroups. Here are some examples:

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/required-children": [
        "error",
        {
          matches: [
            // example 1: require all color.base tokens to be a ramp with 100–900 values
            {
              match: ["color.base.*"],
              requiredTokens: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
            },

            // example 2: require all text.* and bg.* tokens to have specific states
            {
              match: ["text.*", "bg.*"],
              requiredGroups: ["action", "disabled", "error", "warn", "success"],
            },
          ],
        },
      ],
    },
  },
});
OptionDescription
matchesArray of Matches.
matches[n].matchArray of token globs to include, e.g. ["color.*"]
matches[n].requiredTokensArray of strings to match against sub-token IDs, e.g. ["100", "200", …]
matches[n].requiredGroupsArray of strings to match against sub-group IDs, e.g. ["action", "disabled", …]

core/required-modes

Enforce certain tokens have specific modes.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/required-modes": [
        "error",
        {
          matches: [
            {
              match: ["color.*"],
              modes: ["light", "dark"],
            },
          ],
        },
      ],
    },
  },
});
OptionDescription
matchesArray of Matches.
matches[n].matchArray of token globs to include, e.g. ["size.*", "typography.*"]
matches[n].modesArray of strings to match against mode names, e.g. ["mobile", "desktop", …]

core/required-typography-properites

This rule is deprecated in favor of core/valid-typography.

Enforce typography tokens have required properties.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "core/required-typography-properties": [
        "error",
        {
          properties: [
            "fontFamily",
            "fontSize",
            "fontStyle",
            "fontWeight",
            "letterSpacing",
            "lineHeight",
          ],
        },
      ],
    },
  },
});
OptionDescription
propertiesArray of required properties, e.g. ["fontFamily", "fontSize", "letterSpacing", …]
ignoreArray of token globs to ignore, e.g. '["typography.legacy.*"]

a11y/min-contrast

Enforce colors meet minimum contrast checks for WCAG 2. Rather than test every possible combination in your color system, which would lead to lots of false positives, instead you declare your contrast pairs.

Each pair consists of a foreground and background color to test (note that while WCAG 2 doesn’t distinguish between foreground and background, some other color algorithms do). Optionally, you can set largeText = true if this is for large or bold text (which lessens the contrast requirements a bit).

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "a11y/min-contrast": [
        "error",
        {
          level: "AA",
          pairs: [
            {
              foreground: "color.text-primary",
              background: "color.bg-primary",
            },
            {
              foreground: "color.error-text",
              background: "color.error-bg",
            },
            {
              foreground: "color.action-text",
              background: "color.action-bg",
            },
          ],
        },
      ],
    },
  },
});
OptionDescription
levelAA or AAA, corresponding to the WCAG conformance levels
pairsPairs of tokens to test.
pairs[n].foregroundToken ID of the foreground to test (must be a color token)
pairs[n].backgroundToken ID of the foreground to test (must be a color token)
pairs[n].largeText(Optional) Is this large text?

a11y/min-font-size

Enforce font sizes are no smaller than the given value.

import { defineConfig } from "@terrazzo/cli";

export default defineConfig({
  lint: {
    rules: {
      "a11y/min-font-size": ["error", { minSizeRem: 1 }],
    },
  },
});
OptionDescription
minSizePxMin screen pixel size a font may be.
minSizeRemMin rem size a font may be.
ignoreArray of token globs to ignore, e.g. ["legacy.*"]

Writing your own plugin

Writing your own linter is easy! Terrazzo’s lint API is heavily-inspired by ESLint, but even simpler. See the plugin section on linting to get started.

Written a linter yourself? Add it!