A design-system linter that scans repos for JSX, TSX, and Vue components, counts how often each is used (Storybook-style visibility without story files), and reports design-system health signals for the terminal, CI, or a dashboard.
Powered by Oxc for fast JavaScript, TypeScript, and JSX parsing. Vue support uses Oxc on <script> blocks plus template heuristics (not the Vue compiler).
Prebuilt native binaries ship with the dslinter npm package — you do not need Rust or Cargo unless you are contributing to the scanner itself.
This is an alpha release of the project. While it is functional and safe to use, it has known gaps — see ROADMAP.md for the path to beta.
npm install -D dslinter
npx dslinter # local dev: watch + dashboard (Vite app required)
npx dslinter init # scaffold baseline .dslinter.json + optional playground registry files
npx dslinter --report /path/to/repo
npx dslinter --report /path/to/repo --json
npx dslinter --report -p /path/to/repo --fail-on-warnings
npx dslinter --report --output public/dslinter-report.json
npx dslinter --watch --output public/dslinter-report.json
npx dslinter --build # write report + vite build (in a Vite project)In CI (CI=true), bare npx dslinter runs --report (one-shot stdout). Use --report explicitly if your CI does not set CI.
On local first run, npx dslinter auto-creates a starter .dslinter.json when no config exists.
The CLI binary is named dslinter (not dslint) to avoid collision with an unrelated crates.io dslint package.
- Respects ignores:
.gitignoreand.dslinterignoreat the repo root (globset semantics; last matching rule wins, including!negation; use\!for a literal!). Optionalexclude_globsin config apply too. - Inline suppressions: On the line above a finding, use
// dslinter-ignore-next-line rule-id(comma-separated rules;*orprefix*allowed). That line only suppresses the next source line. - Overlap: If
token-hardcoded-colorandtoken-tailwind-arbitraryboth hit the same line, only the Tailwind rule is reported.
Put .dslinter.json at the repository root:
{
"include_dirs": ["src/components"],
"ignore_globs": ["fixtures/**"],
"css_entrypoints": ["src/index.css"],
"deprecated_components": ["LegacyButton"],
"known_tokens": ["--color-", "spacing.", "theme."],
"exclude_globs": ["fixtures/**", "*.generated.tsx"],
"smell": {
"disabled_rules": ["code-todo-marker"],
"report_console_error": true
},
"check_unused_props": false,
"check_dark_mode_contrast": false,
"check_unused_css_tokens": true,
"local_import_prefixes": ["@/", "~/"],
"external_import_patterns": ["@radix-ui/*", "lucide-react"]
}include_dirs restricts component discovery to those directory prefixes.
ignore_globs uses the same ignore semantics as .gitignore/.dslinterignore.
css_entrypoints scopes token analysis to selected CSS entry files (+ their @import graph).
check_unused_props enables unused-prop findings for declared props that have no call-site usage.
check_unused_css_tokens defaults to true (emits token-unused-css-var); set false to disable.
local_import_prefixes marks import path prefixes as local (in-repo) modules.
external_import_patterns excludes matching third-party modules from the component catalog.
smell.disabled_rules accepts code-* rule ids (and legacy smell-* aliases). check_dark_mode_contrast is heuristic: it inspects static class / className strings and string arguments to cn(...), clsx(...), and classnames(...) extracted from the AST where possible.
Legacy exclude_globs remains supported for backwards compatibility.
demo/react— Vite + React + TypeScript + Tailwind sandbox with the dashboard UI. Seedemo/react/README.md.demo/inertia— Laravel 13 + Inertia + React + shadcn/ui. Seedemo/inertia/README.md.
- Definitions: functions, classes,
constarrows,forwardRef/memo, exports - Usage: PascalCase JSX and Vue template usage, with prop lists (variant hints)
- Accessibility:
<img>alt, meaningful<a href>,<button>/<input>/<select>/<textarea>accessible names (JSX AST + Vue<template>heuristics; bareidalone is not treated as an accessible name); governance scoring weights alla11y-*rules - Code quality (
code-*): console/debugger noise, suppressions, TODO markers, large files, inline JSXstyle, emptycatch, redundant fragments — lightly affects maintainability score - Design system: duplicate definitions, deprecated component usage, hardcoded hex (
token-hardcoded-color), Tailwind arbitrary values (token-tailwind-arbitrary), CSS custom-property inventory with used/unused tracking (css_tokensin--json);token-unused-css-varon by default (check_unused_css_tokens) - Scores: heuristic governance scores including optional
token_adoptionwhen CSS tokens orknown_tokensare measurable;--jsonfor dashboards and CI - CSS tokens (
css_tokensin JSON): scans source.css(plus@importtargets likedslinter/theme.css), classifies--color-*/--spacing-*/ etc., and reports references fromvar(--*)and Tailwind utilities
Roadmap follows phased governance (tokens, deeper a11y, drift, AI compliance) — see ROADMAP.md.
Same baseline file as MCP (.dslinter/mcp-baseline.json):
npx dslinter --report . --update-baseline # save scores + finding count
npx dslinter --report . --diff-baseline # print DriftSummary JSON
npx dslinter --report . --fail-on-drift # exit 1 on regressions
npx dslinter --report . --fail-on-drift --max-finding-delta 5 --max-score-drop 2--fail-on-drift fails when finding count rises or any score pillar drops beyond --max-score-drop (default 0).
| Artifact | Version source |
|---|---|
npm dslinter |
dashboard/package.json |
Rust crate / CLI --version |
Cargo.toml (kept in sync with npm) |
@dslinter/binding-* |
Published by CI to match the npm release |
DSLinter ships a Model Context Protocol server so Cursor, Claude Code, and other agents can query the component catalog, usage patterns, findings, and governance policy without parsing the full report JSON.
Add to .cursor/mcp.json in your app root:
{
"mcpServers": {
"dslinter": {
"command": "npx",
"args": ["dslinter", "mcp"],
"cwd": "${workspaceFolder}"
}
}
}For monorepos, set cwd to the app directory (e.g. demo/react/ or demo/inertia/) or set DSLINTER_SCAN_ROOT.
| Tool | Purpose |
|---|---|
dslinter_scan |
Refresh report; return scores and finding counts |
dslinter_get_catalog |
Components sorted by usage |
dslinter_get_component |
Props, variants, findings, example JSX |
dslinter_get_findings |
Filter findings by component, rule, severity, path |
dslinter_get_usage_examples |
Call sites and prop value frequencies |
dslinter_get_tokens |
CSS token definitions and unused tokens |
dslinter_get_agent_context |
Compact context pack for system prompts |
dslinter_get_policy |
Config snapshot + rule catalog |
dslinter_check_paths |
Findings for specific files after edits |
dslinter_diff_since |
Drift vs saved baseline |
dslinter_suggest_fix |
Heuristic fix suggestions |
| Variable | Purpose |
|---|---|
DSLINTER_SCAN_ROOT |
Scan boundary override |
DSLINTER_REPORT_PATH |
Report file override |
DSLINTER_MCP_DEV_URL |
Dev server URL (default http://127.0.0.1:7878) |
DSLINTER_MCP_TTL_MS |
Report cache TTL (default 60000) |
When npx dslinter dev mode is running, the MCP server prefers the live report from port 7878.
cd demo/react && npx dslinter mcp --self-testSee .cursor/skills/dslinter/SKILL.md for agent workflow guidance.
From the repository root:
cargo build --release
cargo test
./target/release/dslinter demo/react --json
cargo run --release --bin dslinter -- demo/react -p --jsonNAPI bindings for npm are built and published by .github/workflows/release-napi-bindings.yml. Maintainers publish with pnpm run release:patch (see dashboard/README.md).
See CONTRIBUTING.md for distribution notes and development workflow.