How to Programmatically Use the DESIGN.md Linter API in Node.js
Import the lint function from @google/design.md/linter and pass a DESIGN.md markdown string to receive a fully typed LintReport containing findings, severity summaries, and a generated Tailwind CSS configuration.
The @google/design.md package exposes a pure‑JavaScript/TypeScript linter that can be embedded directly into build pipelines, CI steps, or custom tooling without invoking the CLI. By importing from the ./linter entry point declared in packages/cli/package.json, you gain access to the same parsing, validation, and Tailwind emission logic used internally by the command‑line tool. All core functions are exported from packages/cli/src/linter/index.ts and provide full type safety for Node.js applications.
Core Architecture and Pipeline
The linter API follows a strict four‑phase pipeline implemented in packages/cli/src/linter/lint.ts. First, the ParserHandler ingests raw markdown and produces a ParsedDesignSystem object. Next, the ModelHandler resolves these tokens into a fully typed DesignSystemState, performing default value inference and cross‑reference checks. The runLinter function then executes the rule set against this state, and finally the TailwindEmitterHandler generates the CSS theme configuration. Each phase is exposed individually, allowing you to intercept or customize specific stages while maintaining type safety.
Basic Programmatic Linting
The simplest integration calls the high‑level lint function exported from @google/design.md/linter. This single call orchestrates parsing, model resolution, rule evaluation, and Tailwind emission.
import { lint } from '@google/design.md/linter';
import { readFileSync } from 'node:fs';
const designMd = readFileSync('path/to/DESIGN.md', 'utf8');
const report = lint(designMd);
// Inspect findings
report.findings.forEach(finding => {
console.log(`[${finding.severity}] ${finding.path ?? ''}: ${finding.message}`);
});
// Access the generated Tailwind config
console.log('Tailwind theme:', report.tailwindConfig);
The lint function automatically uses DEFAULT_RULES from packages/cli/src/linter/rules/index.ts and returns a comprehensive LintReport containing the resolved design system, all findings, severity counts, and the Tailwind configuration object.
Advanced Integration Patterns
Implementing Custom Lint Rules
You can supply your own LintRule functions or replace the default set entirely. Custom rules conform to the signature (state: DesignSystemState) => Finding[] and are merged with any other rules passed to the lint options.
import { lint, type LintRule } from '@google/design.md/linter';
const tempTokenRule: LintRule = (state) => {
const findings = [];
for (const [path, token] of Object.entries(state.tokens ?? {})) {
if (path.includes('temp')) {
findings.push({
severity: 'warning',
path,
message: `Token name contains "temp": ${path}`,
});
}
}
return findings;
};
const report = lint(designMd, { rules: [tempTokenRule] });
The rule runner in packages/cli/src/linter/linter/runner.ts handles both plain functions and descriptor objects, aggregating findings into the final report.
Linting Without Parsing
If you already have a parsed DesignSystemState, you can skip the parsing phase and invoke runLinter directly. This is useful when integrating with custom parsers or caching layers.
import { runLinter, DEFAULT_RULES } from '@google/design.md/linter';
import { ModelHandler } from '@google/design.md/linter/model/handler.js';
// Assume `parsed` is a ParsedDesignSystem obtained elsewhere
const { designSystem } = new ModelHandler().execute(parsed);
// Run only the linting phase with default rules
const result = runLinter(designSystem, DEFAULT_RULES);
console.log('Errors:', result.summary.errors);
Extracting Tailwind Configuration
The linter produces a Tailwind theme object that you can merge into your existing Tailwind configuration. The TailwindEmitterHandler in packages/cli/src/linter/tailwind/handler.ts generates this object during the linting process.
import { lint } from '@google/design.md/linter';
import type { Config } from 'tailwindcss';
const report = lint(designMd);
const generatedTheme = report.tailwindConfig; // { theme: { extend: … } }
const tailwindConfig: Config = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
...generatedTheme.theme.extend,
// add your own extensions here
},
},
};
export default tailwindConfig;
Summary
- Import the
lintfunction from@google/design.md/linterto run the complete pipeline with a single call. - The linter processes markdown through four distinct phases: parsing, model resolution, rule evaluation, and Tailwind emission, as implemented in
packages/cli/src/linter/lint.ts. - Supply custom
LintRulefunctions to thelintoptions to enforce project‑specific constraints. - Use
runLinterdirectly frompackages/cli/src/linter/linter/runner.tswhen you need to lint an already resolvedDesignSystemState. - Access the generated Tailwind theme via
report.tailwindConfigfor immediate use in your CSS build pipeline.
Frequently Asked Questions
What entry point should I import from to use the linter API?
Import from @google/design.md/linter, which is defined as the ./linter conditional export in packages/cli/package.json. This entry point re‑exports all public primitives from packages/cli/src/linter/index.ts, including lint, runLinter, LintReport, and DesignSystemState.
Can I run the linter without generating Tailwind CSS?
Yes. The lint function always produces a tailwindConfig property, but you can ignore it if you only need validation. Alternatively, use runLinter directly from packages/cli/src/linter/linter/runner.ts with your own DesignSystemState to execute only the rule evaluation phase without any emission handlers.
How do I create custom lint rules that access the full design system?
Define a function conforming to the LintRule type: (state: DesignSystemState) => Finding[]. The state object contains the fully resolved tokens, groups, and aliases after the ModelHandler has processed the raw markdown. Return an array of Finding objects with severity, path, and message properties.
Where are the default lint rules defined?
The DEFAULT_RULES array is exported from packages/cli/src/linter/rules/index.ts. These rules are automatically applied when you call lint without specifying a custom rules option, or when you call runLinter with the DEFAULT_RULES import.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →