# How to Use the DESIGN.md Programmatic Linter API in TypeScript

> Learn to use the DESIGN.md programmatic linter API in TypeScript. Parse markdown, resolve tokens, and run custom lint rules within your code.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-06-25

---

**The DESIGN.md programmatic linter API exposes three core functions—`lint()`, `runLinter()`, and `preEvaluate()`—from [`packages/cli/src/linter/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/index.ts), enabling you to parse design system markdown, resolve tokens into a typed model, and execute custom lint rules entirely within TypeScript.**

The [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) repository provides a composable TypeScript library for validating design system documentation. By importing the **DESIGN.md programmatic linter API**, you can integrate design token validation into build pipelines, custom CLI tools, or IDE extensions without spawning external processes.

## Understanding the Linter Architecture

The library implements a four-stage pure functional pipeline implemented across specific handler modules:

1. **Parser** – `ParserHandler` extracts YAML front-matter and H2 sections from raw markdown. Located in [`packages/cli/src/linter/parser/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/parser/handler.ts).
2. **Model** – `ModelHandler` resolves design tokens (colors, typography, dimensions) into a strongly-typed `DesignSystemState`. Located in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts).
3. **Lint Rules** – Pure functions in `packages/cli/src/linter/rules/*.ts` examine the model and emit `Finding` objects. The `runLinter` function in [`packages/cli/src/linter/linter/runner.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/runner.ts) aggregates these into a `LintResult`.
4. **Emitters** – `TailwindEmitterHandler` generates Tailwind CSS configuration. Located in [`packages/cli/src/linter/tailwind/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/tailwind/handler.ts).

The high-level `lint()` function orchestrates this flow: parsing → modeling → linting → Tailwind generation.

## Core API Methods

The public API surface in [`packages/cli/src/linter/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/index.ts) exports three primary entry points:

**`lint(content: string, options?: LintOptions): LintReport`**

Parses a DESIGN.md file string, resolves the design system model, runs default lint rules, and returns a full report including Tailwind configuration.

**`runLinter(state: DesignSystemState, rules?: LintRule[] | RuleDescriptor[]): LintResult`**

Executes lint rules on an already-resolved design system state. Use this when caching models or running custom rule sets.

**`preEvaluate(state: DesignSystemState, rules?: LintRule[] | RuleDescriptor[]): GradedTokenEdits`**

Groups findings by severity (`error`, `warning`, `info`) into a structure suitable for automated fixes or UI suggestions.

## TypeScript Implementation Examples

### Basic Usage: Linting a DESIGN.md String

Import the `lint` function and pass your markdown content as a string:

```typescript
import { lint } from '@design/cli';

const designMd = await Bun.file('my-design.md').text();

const report = lint(designMd);

console.log('Errors:', report.summary.errors);
console.log('Warnings:', report.summary.warnings);
console.log('Tailwind config:', report.tailwindConfig);

```

This executes the complete pipeline defined in [`packages/cli/src/linter/lint.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/lint.ts).

### Advanced Usage: Custom Rules and Model Reuse

For scenarios requiring custom validation logic or repeated linting against the same design system, manually orchestrate the pipeline:

```typescript
import {
  ParserHandler,
  ModelHandler,
  runLinter,
  preEvaluate,
  type LintRule,
} from '@design/cli';

const designMd = await Bun.file('design.md').text();

const parser = new ParserHandler();
const parseResult = parser.execute({ content: designMd });

if (!parseResult.success) {
  throw new Error('Failed to parse DESIGN.md');
}

const model = new ModelHandler();
const { designSystem, findings: modelFindings } = model.execute(parseResult.data);

const noRedRule: LintRule = (state) => {
  const redTokens = state.tokens.filter(t => t.value === '#ff0000');
  return redTokens.map(t => ({
    severity: 'error' as const,
    path: t.path,
    message: `Avoid using hard‑coded red color at ${t.path}`,
  }));
};

const lintResult = runLinter(designSystem, [noRedRule]);
const graded = preEvaluate(designSystem, [noRedRule]);

console.log('Custom errors:', lintResult.summary.errors);
console.log('Fix suggestions:', graded.fixes);

```

Custom rules must conform to the `LintRule` signature `(state: DesignSystemState) => Finding[]`. The `runLinter` function accepts either rule function arrays or `RuleDescriptor` objects.

### Configuring Your TypeScript Project

Ensure your [`tsconfig.json`](https://github.com/google-labs-code/design.md/blob/main/tsconfig.json) supports ES modules:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "node16",
    "esModuleInterop": true,
    "strict": true
  }
}

```

## Key Source Files and Entry Points

- **[`packages/cli/src/linter/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/index.ts)** – Public API surface exporting `lint`, `runLinter`, and `preEvaluate`.
- **[`packages/cli/src/linter/lint.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/lint.ts)** – Orchestrates the complete parse → model → lint → Tailwind pipeline.
- **[`packages/cli/src/linter/linter/runner.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/runner.ts)** – Pure functional runner implementing `runLinter` and `preEvaluate`.
- **[`packages/cli/src/linter/parser/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/parser/handler.ts)** – Markdown parsing logic.
- **[`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts)** – Design token resolution into `DesignSystemState`.
- **[`packages/cli/src/linter/rules/types.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/rules/types.ts)** – TypeScript interfaces for `LintRule` and `Finding`.
- **[`packages/cli/src/linter/rules/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/rules/index.ts)** – Default rule set (`DEFAULT_RULES`).
- **[`packages/cli/src/commands/lint.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/commands/lint.ts)** – Reference implementation showing how the CLI consumes the programmatic API.

## Summary

- The **DESIGN.md programmatic linter API** provides three main entry points: `lint()` for full pipeline execution, `runLinter()` for rule execution against cached models, and `preEvaluate()` for severity-graded findings.
- The architecture separates concerns into Parser, Model, Lint Rules, and Emitters, allowing granular control over the validation process.
- Custom lint rules are pure functions implementing the `LintRule` interface, enabling domain-specific validation beyond the default rule set.
- All components are implemented in pure TypeScript and importable from `@design/cli` without CLI dependencies.

## Frequently Asked Questions

### What is the difference between `lint()` and `runLinter()`?

Use `lint()` when you have raw markdown content and need the complete pipeline including parsing and Tailwind generation. Use `runLinter()` when you already have a resolved `DesignSystemState` and want to execute rules against it, which is more efficient for repeated validation or custom rule testing.

### How do I define a custom lint rule?

Custom rules implement the `LintRule` type from [`packages/cli/src/linter/rules/types.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/rules/types.ts). They receive the `DesignSystemState` and return an array of `Finding` objects with `severity`, `path`, and `message` properties. Pass your rule array to `runLinter()` or `preEvaluate()`.

### Can I use the linter without generating Tailwind configuration?

Yes. The `runLinter()` and `preEvaluate()` functions perform pure linting without invoking the Tailwind emitter. Only the high-level `lint()` function includes Tailwind generation in its output.

### What TypeScript module resolution should I use?

Configure `moduleResolution: "node16"` or `"bundler"` in your [`tsconfig.json`](https://github.com/google-labs-code/design.md/blob/main/tsconfig.json) along with `"module": "ESNext"` to properly resolve the `@design/cli` exports, as the library ships with ES module syntax.