# How to Use MDX Expressions in DESIGN.md Files with the `spec` Command

> Learn how to use MDX expressions in DESIGN.md files with the spec command. This tool evaluates JavaScript expressions to generate markdown output for your documentation.

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

---

**MDX expressions in `spec.mdx` files are evaluated by the `npx @google/design.md spec` command using a sandboxed compiler that replaces JavaScript expressions with their computed markdown output, generating the final [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md) specification.**

The [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) repository provides a CLI tool that transforms MDX source files into human-readable specifications. When you run the `spec` command, it processes embedded MDX expressions in your `spec.mdx` file to dynamically generate content based on your configuration and reusable renderer functions.

## Understanding the MDX Compilation Pipeline

The `spec` command triggers a three-stage pipeline that converts MDX syntax into static markdown. Understanding these stages helps you write effective expressions and debug compilation issues.

### Entry Point and Scope Generation

In [`packages/cli/src/linter/spec-gen/generate.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/generate.ts), the generation process begins by reading the `spec.mdx` source file and constructing a **scope** object. This scope binds two critical resources: the `SPEC_CONFIG` configuration data and a set of pure-function **renderers** imported from [`renderers.ts`](https://github.com/google-labs-code/design.md/blob/main/renderers.ts).

The [`generate.ts`](https://github.com/google-labs-code/design.md/blob/main/generate.ts) module orchestrates the workflow:

1. Reads `spec.mdx` (referenced as `MDX_PATH`)
2. Builds a scope containing `SPEC_CONFIG` and renderer functions
3. Calls `compileMdx(source, scope)` to transform the MDX into plain markdown
4. Writes the generated output to [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md)

### AST Parsing and Expression Evaluation

The [`packages/cli/src/linter/spec-gen/compiler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/compiler.ts) module handles the actual MDX processing. It uses the **unified** ecosystem with `remark-parse` and `remark-remark-mdx` to parse the source into an AST.

When the compiler encounters expression nodes, it evaluates them using a sandboxed `Function` constructor:

- **Inline expressions** (`mdxTextExpression` nodes like `{sectionOrderList()}`) execute within the scope and return strings that replace the node directly
- **Block expressions** (`mdxFlowExpression` nodes like `{colorsExample()}`) execute similarly but wrap results in HTML nodes to preserve markdown block formatting

### Renderer Functions

The [`packages/cli/src/linter/spec-gen/renderers.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/renderers.ts) file contains pure functions that generate reusable markdown fragments. Each renderer receives the fully typed `SpecConfig` object and returns a markdown string. These functions are exposed to the MDX scope, allowing you to call them directly within curly braces.

## How MDX Expressions Are Evaluated

The compiler distinguishes between two expression types based on their position in the document:

| Expression Type | Node Type | Output Handling | Use Case |
|-----------------|-----------|-----------------|----------|
| Inline | `mdxTextExpression` | Replaced with plain text node | Dynamic values inside paragraphs |
| Block | `mdxFlowExpression` | Wrapped in HTML node preserving markdown | Tables, code blocks, lists |

Because the evaluator uses `new Function(...scope)`, expressions can only reference variables explicitly provided in the scope. You cannot import arbitrary modules or access Node.js globals outside the defined scope.

## Practical Examples

### Inline Expressions for Dynamic Values

Use curly braces within text to inject configuration values or simple renderer output:

```mdx

# DESIGN.md Format

The current spec version is **{SPEC_VERSION}**.

{sectionOrderList()}

```

When processed, `{SPEC_VERSION}` pulls from `SPEC_CONFIG`, while `{sectionOrderList()}` calls the renderer to generate a numbered list:

```markdown

# DESIGN.md Format

The current spec version is **alpha**.

1. Overview
2. Colors
3. Typography
4. Layout
5. Elevation & Depth
6. Shapes
7. Components
8. Do's and Don'ts

```

### Block-Level Expressions for Complex Content

For tables, code blocks, or multi-line content, use block expressions that occupy their own line:

```mdx

## Colors

{colorsExample()}

```

The `colorsExample()` renderer returns a fenced code block with YAML front matter. The compiler preserves the markdown formatting, producing:

```markdown

## Colors

```yaml
---
version: alpha
name: Daylight Prestige
colors:
  primary: "#1A1C1E"
  secondary: "#6C7278"
  tertiary: "#B8422E"
typography:
  h1:
    fontFamily: Public Sans
    fontSize: 48px
    fontWeight: 600
    lineHeight: 1.1
    letterSpacing: -0.02em
---

```

```

### Creating Custom Renderers

To add domain-specific content, extend the rendering system in three steps.

First, define the function in [`packages/cli/src/linter/spec-gen/renderers.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/renderers.ts):

```typescript
export function myCustomBanner(cfg: SpecConfig): string {
  return `> **NOTE:** This spec was generated on ${new Date().toDateString()} using version ${cfg.SPEC_VERSION}.`;
}

```

Second, expose it in [`packages/cli/src/linter/spec-gen/generate.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/generate.ts) by adding to the scope:

```typescript
myCustomBanner: () => renderers.myCustomBanner(cfg),

```

Third, invoke it in `spec.mdx`:

```mdx
{myCustomBanner()}

```

The compiled output will contain the dynamic banner with the current date and version string.

## Running the `spec` Command

Execute the generation process from your project root:

```bash
npx @google/design.md spec

```

This command reads the default `spec.mdx` path, compiles the MDX expressions using the pipeline described above, and writes the final specification to [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md). The command also supports outputting to stdout or custom file paths as documented in the repository README under the **`spec`** heading.

## Summary

- **MDX expressions** in `spec.mdx` enable dynamic content generation by executing JavaScript within a sandboxed scope during the `spec` command execution.
- The **compilation pipeline** in `packages/cli/src/linter/spec-gen/` consists of [`generate.ts`](https://github.com/google-labs-code/design.md/blob/main/generate.ts) for orchestration, [`compiler.ts`](https://github.com/google-labs-code/design.md/blob/main/compiler.ts) for AST processing, and [`renderers.ts`](https://github.com/google-labs-code/design.md/blob/main/renderers.ts) for markdown generation functions.
- **Two expression types** exist: inline expressions (`mdxTextExpression`) for text replacement and block expressions (`mdxFlowExpression`) for complex markdown structures.
- **Custom renderers** extend functionality by adding pure functions to [`renderers.ts`](https://github.com/google-labs-code/design.md/blob/main/renderers.ts) and exposing them in the scope within [`generate.ts`](https://github.com/google-labs-code/design.md/blob/main/generate.ts).
- The `npx @google/design.md spec` command processes these expressions to produce the final human-readable [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md) specification.

## Frequently Asked Questions

### What is the difference between inline and block MDX expressions?

Inline expressions use `mdxTextExpression` nodes and work within paragraphs or headings, returning text that replaces the curly braces directly. Block expressions use `mdxFlowExpression` nodes and stand alone on their own lines, returning markdown that the compiler wraps in HTML nodes to preserve formatting. According to the source code in [`compiler.ts`](https://github.com/google-labs-code/design.md/blob/main/compiler.ts), both execute within the same sandboxed scope but differ in how the AST transformer handles their output.

### Can I import external modules in MDX expressions?

No. The compiler in [`packages/cli/src/linter/spec-gen/compiler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/compiler.ts) uses a `new Function(...scope)` constructor that limits expression evaluation to the provided scope variables. You cannot use `import`, `require`, or reference Node.js globals. All data must come from `SPEC_CONFIG` or renderer functions exposed in the scope by [`generate.ts`](https://github.com/google-labs-code/design.md/blob/main/generate.ts).

### How do I add custom rendering logic?

Create a pure function in [`packages/cli/src/linter/spec-gen/renderers.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/renderers.ts) that accepts the `SpecConfig` type and returns a markdown string. Then expose that function in the scope object within [`packages/cli/src/linter/spec-gen/generate.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-gen/generate.ts). Finally, call the function using curly braces in `spec.mdx`. The function will execute during compilation and its return value will appear in the generated specification.

### Where does the `spec` command write its output?

By default, the `spec` command writes the compiled markdown to [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md) in your project directory. This behavior is controlled by the [`generate.ts`](https://github.com/google-labs-code/design.md/blob/main/generate.ts) entry point, which reads from `spec.mdx` (referenced as `MDX_PATH`) and outputs the processed content. You can verify the output path and override options in the README documentation under the `spec` command section.