dtlint-core
v0.2.0
Published
Design token linter: flags hardcoded hex/px/font values and suggests the nearest design token
Readme
dtlint — Design Token Linter
Scans React / Vue / CSS / Tailwind codebases for hardcoded hex colors, px measurements, and generic font stacks — and suggests the nearest official design token using perceptual CIEDE2000 (Delta-E) color matching, not exact string comparison.
src/styles.css
3:10 error #1a73e8 (color) → use color-brand-blue [ΔE 0] (auto-fixable)
6:28 error #3b82f6 (border-bottom) → use color-brand-blue [ΔE 5.66]
12:11 warn 17px (margin) → use space-4 [Δ 1px]
16:16 error Arial, sans-serif (font-family) → use font-body
23:15 error #00ff9c (background) → no token within threshold — needs a design decisionWhy Delta-E? #1b74e9 is one RGB step away from your brand blue #1a73e8 — no
human can tell them apart (ΔE 0.38), so dtlint auto-fixes it. #3b82f6 looks similar
but is perceptibly different (ΔE 5.66), so dtlint suggests the token and leaves the
decision to you. String-matching linters can't make that distinction.
Packages
| Package | Registry | What it is |
|---|---|---|
| dtlint | npm | CLI: scan, check (CI gate), fix, baseline |
| @dtlint/core | npm | Engine: token ingestion, Delta-E matching, scanners, fix engine, SARIF |
| @dtlint/language-server | npm | LSP server — VS Code, Neovim, JetBrains, any LSP client |
| dtlint-vscode | VS Code Marketplace | Extension: live diagnostics, quick fixes, fix-all, token hot reload |
Quick start
npm install -D dtlint # once published; from source: npm install && npm run build- Export your design tokens from Figma (via Tokens Studio or figmage) as DTCG or Style Dictionary JSON.
- Create
.dtlintrc.jsonat your repo root:
{
"tokens": ["./design-tokens/tokens.json"],
"rules": {
"hardcoded-color": { "severity": "error", "autofixDeltaE": 2, "suggestDeltaE": 10 },
"hardcoded-spacing": { "severity": "warn", "autofixDeltaPx": 0, "suggestDeltaPx": 4 },
"generic-font": { "severity": "error" }
},
"ignore": ["**/*.stories.tsx", "**/generated/**"]
}- Run it:
npx dtlint scan src # report findings with suggestions
npx dtlint fix src --dry-run # preview auto-fixes, then run without --dry-run
npx dtlint check src # CI mode: exit 1 on violationsTry it on the bundled fixture: npm run lint:self.
Configuration reference
| Key | Default | Meaning |
|---|---|---|
| tokens | — (required) | Paths to token JSON files, relative to the config file. DTCG ($value/$type, {alias} references) and legacy Style Dictionary (value/type) both supported. |
| rules.hardcoded-color.severity | error | off / warn / error |
| rules.hardcoded-color.autofixDeltaE | 2 | Matches at or below this ΔE are safe to auto-fix (imperceptible difference) |
| rules.hardcoded-color.suggestDeltaE | 10 | Matches beyond autofix but within this bound are suggested for review; farther values are flagged with no candidate |
| rules.hardcoded-spacing.autofixDeltaPx / suggestDeltaPx | 0 / 4 | Same two-threshold model for the spacing scale |
| rules.hardcoded-spacing.properties | margin/padding/gap/inset/border-radius/… | CSS properties whose px values are checked |
| rules.generic-font.severity | error | Font stacks whose primary family isn't token-approved |
| ignore | node_modules, dist, build | Glob patterns to skip (extends, not replaces, defaults) |
| replacement.css / replacement.js | var(--{name}) | Replacement template; {name} = kebab-case token path |
What gets scanned
- CSS / SCSS / LESS — hex,
rgb()/hsl()literals; px on spacing properties;font-family - JS / TS / JSX / TSX —
style={{ }}objects (string and React numeric px values), color-valued attributes (fill="#d93025"),styled.*/csstagged template literals, Tailwind arbitrary values (bg-[#1A73E8],p-[17px]) inclassName - Vue SFCs —
<style>blocks,<script>blocks,<template>inlinestyle=""attributes and Tailwind arbitrary values inclass=""
Tailwind arbitrary values are suggested but never auto-rewritten — the correct theme class name can't be derived from the token file alone.
Commands
dtlint scan [paths...] report findings (--format human|json|sarif, --output <file>)
dtlint check [paths...] CI gate: exit 1 on error-severity findings (--max-warnings <n>)
dtlint fix [paths...] apply auto-fixes above the confidence threshold (--dry-run)
dtlint baseline [paths...] snapshot existing violations as accepted debtIncremental adoption (baseline)
Legacy codebases have hundreds of pre-existing violations. Snapshot them once:
npx dtlint baseline src # writes .dtlint-baseline.json beside your config
git add .dtlint-baseline.jsonFrom then on scan/check report only new violations — CI stays green on old debt
and blocks regressions. Fingerprints are line-number-independent, so moving code doesn't
resurface baselined findings. Use --no-baseline to see everything; delete the file to
reset.
CI: blocking PRs
See examples/github-action/dtlint.yml for both options:
- Composite action —
uses: nandkk05/dtlint@v1withpaths/max-warningsinputs. - SARIF upload —
dtlint scan --format sarif+github/codeql-action/upload-sarifrenders findings as inline annotations on the PR diff and in the Security tab.
Editor integration
VS Code — install the dtlint extension (or grab the .vsix from Releases). It runs
the language server automatically: live diagnostics, quick fixes (Replace #1A73E8 with
color-brand-blue), a dtlint: Fix all command, source.fixAll on-save support, and
hot reload when your token file changes.
Any other LSP editor (Neovim, JetBrains, Helix, …) — the server is a standalone npm package speaking stdio:
npm install -g @dtlint/language-server
dtlint-language-server --stdioExample Neovim (lspconfig-style) setup:
vim.lsp.config('dtlint', {
cmd = { 'dtlint-language-server', '--stdio' },
filetypes = { 'css', 'scss', 'less', 'javascriptreact', 'typescriptreact', 'vue' },
root_markers = { '.dtlintrc.json', '.dtlintrc', 'dtlint.config.json' },
})Architecture
packages/
core/ token ingestion → Delta-E matcher → scanners → fix engine → SARIF/baseline
cli/ dtlint scan/check/fix/baseline (thin wrapper over core)
language-server/ LSP server over core (diagnostics, code actions, fix-all)
vscode/ LSP client + esbuild bundle for the MarketplaceBoth the CLI and every editor share the same engine and the same .dtlintrc.json, so
the IDE and CI can never disagree about what's a violation.
Contributing & releasing
- CONTRIBUTING.md — dev setup, project layout, PR guidelines
- docs/publishing.md — npm + VS Code Marketplace release process
- CHANGELOG.md
