How to Programmatically Use the Linter API in Node.js with @google/design.md
The @google/design.md package exposes a pure-JavaScript linter API that parses DESIGN.md documents and generates Tailwind CSS configurations via the lint(content, options?) function.
The google-labs-code/design.md repository provides a framework-agnostic linting pipeline for design system documentation. By importing the linter API in Node.js, you can programmatically validate DESIGN.md files, resolve design tokens into a typed state, and emit Tailwind configurations without shelling out to a CLI process.
Importing the Linter Entry Point
The package exposes its programmatic interface through the ./linter subpath export defined in packages/cli/package.json (lines 31-34). This entry point re-exports the core lint function and type definitions from packages/cli/src/linter/index.ts.
Install the package via npm:
npm i @google/design.md
Import the linter using ES modules (recommended) or CommonJS:
// ES Modules
import { lint } from '@google/design.md/linter';
// CommonJS
const { lint } = require('@google/design.md/linter');
Architecture of the Linting Pipeline
The linter API orchestrates four distinct layers to transform raw markdown into a validated design system model. According to the source code in packages/cli/src/linter/lint.ts (lines 56-71), the lint function executes the following flow:
- ParserHandler (
packages/cli/src/linter/parser/handler.ts) – Parses markdown and extracts YAML front-matter, sections, and raw headings. - ModelHandler (
packages/cli/src/linter/model/handler.ts) – Transforms parsed tokens into a typedDesignSystemState. - Rule Runner (
packages/cli/src/linter/linter/runner.ts, lines 30-40) – ExecutesrunLinter(state, rules?)against the default rule set (DEFAULT_RULES) or custom rules you provide. - TailwindEmitterHandler (
packages/cli/src/linter/tailwind/handler.ts) – Converts the resolved design system into a ready-to-use Tailwind CSS configuration object.
The result is a LintReport object (defined in packages/cli/src/linter/lint.ts, lines 30-44) containing the designSystem model, an array of findings, a severity summary, and the generated tailwindConfig.
Running the Linter Programmatically
To lint a DESIGN.md file, read the content into memory and pass it to the lint function. The API has no runtime dependencies beyond peer dependencies like yaml and unified, making it safe for CI pipelines and server-side services.
import { readFile } from 'node:fs/promises';
import { lint } from '@google/design.md/linter';
// Load the DESIGN.md content
const designMd = await readFile('path/to/DESIGN.md', 'utf8');
// Run the linter with default rules
const report = lint(designMd);
You can optionally pass a custom rules array via the options parameter:
import { myCustomRule } from './my-rules.js';
const report = lint(designMd, { rules: [myCustomRule] });
The main execution flow (lines 91-107 in packages/cli/src/linter/lint.ts) handles markdown parsing, model resolution, rule execution, and Tailwind config generation in a single synchronous call.
Handling the LintReport Output
The LintReport returned by the API provides structured data for validation results and theme extraction. Access the severity summary and individual findings to enforce quality gates:
console.log(`Errors: ${report.summary.errors}`);
console.log(`Warnings: ${report.summary.warnings}`);
if (report.findings.length) {
for (const f of report.findings) {
console.log(`[${f.severity}] ${f.path ?? '(global)'} – ${f.message}`);
}
}
To integrate with a build pipeline, write the generated Tailwind configuration to a file that the Tailwind CLI can consume:
import { writeFile } from 'node:fs/promises';
const tailwindConfig = `module.exports = ${JSON.stringify(report.tailwindConfig, null, 2)};`;
await writeFile('tailwind.config.cjs', tailwindConfig);
The tailwindConfig property contains the complete theme object derived from your DESIGN.md tokens, ready for immediate use by Tailwind CSS.
Summary
- Import the linter API from
@google/design.md/linterto access thelintfunction. - The pipeline parses markdown via ParserHandler, resolves tokens via ModelHandler, validates via
runLinterinpackages/cli/src/linter/linter/runner.ts, and emits configs via TailwindEmitterHandler. - Pass a string of DESIGN.md content to
lint()and receive aLintReportwithdesignSystem,findings,summary, andtailwindConfig. - The API is framework-agnostic, runs synchronously, and requires no external CLI invocation.
Frequently Asked Questions
Can I use the linter API with CommonJS?
Yes. While ES modules are recommended, the package supports CommonJS via require('@google/design.md/linter'). The ./linter export in packages/cli/package.json provides the same surface for both module systems.
What Node.js version is required?
The package requires Node.js 18 or higher. The linter API uses native Node.js APIs like node:fs/promises and modern JavaScript features without additional polyfills.
How do I add custom lint rules?
Pass a rules array in the options object to the lint function. Each rule should conform to the rule interface used by runLinter in packages/cli/src/linter/linter/runner.ts (lines 30-40). Custom rules execute alongside the DEFAULT_RULES set unless explicitly disabled.
Does the API require Tailwind CSS to be installed?
No. The TailwindEmitterHandler generates a configuration object compatible with Tailwind CSS, but the linter itself has no runtime dependency on Tailwind. You can use the API to validate DESIGN.md files without ever generating CSS, or extract the tailwindConfig to use with your own Tailwind installation separately.
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 →