---
title: "`void` command reference (audit)"
description: "Written from packages/cli/src/cli/*.ts (void 0.1.0, 2026-09-16). If void --help disagrees with this file, trust void --help and report the drift."
canonical: https://void-design.vercel.app/docs/audit/commands
lastModified: 2026-09-16
---

# `void` command reference

Written from `packages/cli/src/cli/*.ts` (void 0.1.0, 2026-09-16). If `void --help` disagrees with this file, trust `void --help` and report the drift.

Install and run: the plugin ships the CLI in `packages/cli` (Bun ≥ 1.2). Run `bunx void …` or `bun packages/cli/src/index.ts …` from a checkout, or `void …` when it's linked or installed globally. It needs a Chromium: resolved from `VOID_CHROMIUM` → `CHROME_PATH` → system paths (`/usr/bin/chromium`, Chrome.app…) → the Playwright cache. `void doctor` shows what it found.

## Commands

| Command | Purpose | Exit |
|---|---|---|
| `void audit [url]` | Run every page category: perf, smooth, a11y, seo, geo, design (+ lint when `src` is set) | 0/1/2/3/4 |
| `void perf [url]` | Only performance (web-vitals + resources [+ Lighthouse]) | same |
| `void smooth [url]` | Only smoothness (scripted scroll, interactions, animations, reduced motion, rAF, WebGL) | same |
| `void a11y [url]` | Only accessibility (axe WCAG 2.2 AA, focus visibility, target size, reflow) | same |
| `void seo [url]` | Only SEO (status, head, canonical, links, OG, icons, robots, sitemap) | same |
| `void geo [url]` | Only AI search (raw vs rendered content, JSON-LD, AI robots, llms.txt, Markdown mirrors) | same |
| `void design [url]` | Only design heuristics (type, color, spacing, overflow, slop tells) | same |
| `void lint [dir]` | Static source checks, no browser. `dir` defaults to config `src`, else `.` | 0/1/2 |
| `void rules` | List all rules | 0 |
| `void rules <id>` | Show one rule: title, why, fix, example, refs | 0 / 2 if unknown (suggests near matches) |
| `void rules <category\|prefix>` | List a category (`void rules perf`) or prefix (`void rules seo/canonical`) | 0 |
| `void report <report.json>` | Re-render a saved JSON report (`--format md\|pretty\|json`) | 0 if report passed, else 1 |
| `void extract <url>` | Extract a site's design DNA (tokens, type, color) to `dna.json`, `brief.md`, `direction.css`. *(Module in progress: may print "not available in this build")* | 0/2/4 |
| `void doctor` | Check Bun, Chromium launch, playwright-core, axe-core, web-vitals, optional Lighthouse, config | 0/2/4 |

`url` may be omitted when `--start` or config `url`/`start` provides it. Bare hosts get a scheme: `localhost:3000` → `http://`, `example.com` → `https://`.

## Audit flags (audit and the category shorthands)

| Flag | Values / default | Notes |
|---|---|---|
| `--only` | `perf,smooth,a11y,seo,geo,design,lint` | Comma list. The shorthands set this for you |
| `--profile` | `mobile` \| `desktop` \| `both` (default both) | mobile = 412×823 @2.625x, 4× CPU, 150 ms RTT, 1.6/0.75 Mbps; desktop = 1350×940, unthrottled |
| `--routes` | `/,/pricing` | Paths resolved against the URL. Default: the URL's own path |
| `--runs` | 1–20 (default 3) | Perf runs per profile; the median is reported. Use 5 near budgets, 1 for quick iteration |
| `--lighthouse` | boolean | Also run Lighthouse 13 (optional peer: `bun add -d lighthouse`) |
| `--start` | `"bun run start -p 3100"` | Spawns the command (with `PORT` and `NODE_ENV=production` set), waits for 2xx on `readyPath`, audits, then kills the process group. **Requires `--port`** |
| `--port` | 1–65535 | Port the started server listens on. The URL becomes `http://localhost:<port>` |
| `--src` | `./src` | Also lint this directory as part of the audit |
| `--kind` | `marketing` \| `app` | Selects default budgets (JS 170 vs 300 KB, etc.) |
| `--screenshots` | dir | Save fold + full-page screenshots (design audit) |
| `--format`, `-f` | `pretty` (TTY default) \| `md` \| `json` | `md` = compact for agents, grouped by rule |
| `--json` | boolean | Same as `--format json`; only JSON on stdout, progress on stderr |
| `--out`, `-o` | `report.json` \| `report.md` | Also write the report to a file (format from extension) |
| `--max-per-rule` | number (md default 5) | Occurrences listed per rule |
| `--fail-on` | `error` (default) \| `warn` \| `info` \| `never` | Exit 1 threshold. Budgets also fail unless `never` |
| `--config`, `-c` | path | Default: `void.config.ts` / `.mts` / `.js` / `.mjs` in cwd |
| `--quiet`, `-q` | boolean | No progress output |
| `--no-color` | boolean | Plain output |
| `--help`, `-h` / `--version`, `-v` | | |

Lint uses `--config`, `--fail-on`, `--format`/`--json`, `--out`, `--no-color`.
Extract uses `--out <dir>`, `--screenshots <dir>`, `--pages /,/pricing`, `--max-pages N`, `--format md|css|json`, `--quiet`.

Environment: `VOID_CHROMIUM` / `CHROME_PATH` (browser binary), `VOID_DEBUG=1` (stack traces), `NO_COLOR`, `CI` (plain progress lines).

## Exit codes

| Code | Meaning | What to do |
|---|---|---|
| 0 | Pass: no findings at or above `--fail-on`, all budgets pass | Done (check stopping criteria) |
| 1 | Findings ≥ `--fail-on`, or a budget exceeded | Fix (see the `audit` SKILL loop) |
| 2 | Usage or config error (bad flag, invalid `void.config.ts`, unknown rule id) | Read the message and hint |
| 3 | Target unreachable, or `--start` server exited / timed out | Check the build, port and `readyPath`; the last 10 server log lines are printed |
| 4 | Browser launch failure | `void doctor`; install Chromium or set `VOID_CHROMIUM` |

## `void.config.ts` schema (`VoidConfig`)

| Key | Type | Default | Meaning |
|---|---|---|---|
| `url` | string (absolute) | — | Base URL when none is passed |
| `routes` | string[] | URL path | Paths to audit |
| `start` | `{ command, port, cwd?, readyPath?, timeoutMs? }` | — | Server to spawn (`readyPath` "/", timeout 120 s) |
| `src` | string | — | Source dir for lint (used by `void lint` and added to `void audit`) |
| `profiles` | `("mobile" \| "desktop")[]` | both | |
| `runs` | 1–20 | 3 | Perf runs per profile |
| `kind` | `"marketing" \| "app"` | marketing | Default budget set |
| `budgets` | `{ mobile?: Budgets, desktop?: Budgets }` | see below | Merged over the defaults per key |
| `rules` | `Record<ruleId, "error" \| "warn" \| "info" \| "off">` | — | Severity overrides |
| `failOn` | `"error" \| "warn" \| "info" \| "never"` | error | |
| `lighthouse` | boolean | false | |
| `screenshots` | string | — | Directory |
| `only` | Category[] | all page categories | |

`Budgets` keys: `lcp`, `inp`, `tbt`, `ttfb` (ms) · `cls` · `jsKb`, `cssKb`, `fontKb`, `imageKb`, `htmlKb` (KB transferred at load) · `requests` · `droppedFramePct` (%).

Default budgets:

| | lcp | cls | inp | tbt | ttfb | jsKb | cssKb | fontKb | imageKb | htmlKb | droppedFramePct |
|---|---|---|---|---|---|---|---|---|---|---|---|
| marketing mobile | 2500 | 0.1 | 200 | 200 | 800 | 170 | 25 | 120 | 1000 | 30 | 5 |
| marketing desktop | 1500 | 0.1 | 200 | 200 | 800 | 170 | 25 | 120 | 1500 | 30 | 5 |
| app mobile | 2500 | 0.1 | 200 | 300 | 800 | 300 | 40 | 120 | — | 60 | 5 |
| app desktop | 1500 | 0.1 | 200 | 200 | 800 | 300 | 40 | 120 | — | 60 | 5 |

The config file may `export default` a plain object (as the template does, with a JSDoc `@type {import('@void/cli/config').VoidConfig}`) or use `defineConfig` from `@void/cli/config` when the package is installed.

## Report JSON (`--json`), abridged

```ts
{
  schemaVersion: 1,
  tool: { name: "void", version: "0.1.0" },
  targets: [{ url, profile, status }],
  scores: { perf, smooth, a11y, seo, geo, design, lint },   // 0–100; gates cap a category at 49
  overall: number,                                          // weighted: perf .25 a11y .2 smooth .15 seo .15 design .15 geo .1
  metrics: [{ name: "lcp", value: 1830, unit: "ms", profile: "mobile", url, runs: [..], budget: 2500 }],
  budgets: [{ name, limit, actual, pass, profile, url }],
  findings: [{
    id: "perf/lcp-image-lazy", category: "perf", severity: "error",
    message: "LCP image /hero.avif has loading=lazy …",
    source: "web-vitals" | "axe" | "lighthouse" | "loaf" | "trace" | "cdp" | "dom" | "ast" | "css" | "http" | "heuristic",
    target: { url, profile, selector, html, file, line, column, snippet },
    evidence: { metric: { name, value, unit, threshold }, data: {…} },
    fix: { hint, example }, docs: "https://void-design.vercel.app/rules/perf/lcp-image-lazy", fingerprint
  }],
  errors: [{ category, message, url, profile }],            // audits that could not complete
  failOn: "error",
  summary: { errors, warnings, infos, pass }
}
```

Useful `jq`:

```bash
void audit --json --start "bun run start -p 3100" --port 3100 > report.json
jq -r '.findings | group_by(.id)[] | "\(.[0].severity)\t\(length)\t\(.[0].id)"' report.json | sort -r   # counts per rule
jq '.budgets[] | select(.pass == false)' report.json
jq '.scores' report.json
```

## Rule id prefixes

| Prefix | Examples |
|---|---|
| `perf/` | `lcp-slow`, `cls-high`, `tbt-high`, `ttfb-slow`, `js-budget`, `lcp-image-lazy`, `lcp-image-no-priority`, `lcp-background-image`, `images-missing-dimensions`, `third-party-heavy`, `bfcache-blocked`, `cache-headers`, `no-text-compression` |
| `smooth/` | `scroll-jank`, `long-frames-during-scroll`, `inp-slow`, `layout-shift-after-input`, `animate-layout-property`, `reduced-motion-ignored`, `raf-loop-idle`, `multiple-webgl-contexts` |
| `a11y/` | axe ids (`color-contrast`, `button-name`, `label`, `link-name`, `image-alt`, `heading-order`, `landmark-one-main`, `meta-viewport`, `bypass`…), plus `target-size`, `focus-not-visible`, `reflow-overflow`, `axe-other` |
| `seo/` | `http-status`, `soft-404`, `metadata-in-body`, `canonical-missing`, `canonical-not-self`, `title-length`, `meta-description-missing`, `h1-count`, `og-image-invalid`, `favicon`, `robots-blocks-all`, `sitemap-lastmod`, `broken-links` |
| `geo/` | `js-dependency`, `hidden-streamed-content`, `jsonld-parse-error`, `jsonld-required`, `jsonld-not-visible`, `faq-deprecated`, `search-bots-blocked`, `llms-txt-missing`, `markdown-mirror-indexable`, `answer-first`, `no-citations` |
| `design/` | `too-many-font-families`, `no-type-scale`, `line-length-long`, `palette-sprawl`, `multiple-accents`, `gradient-text`, `purple-gradient`, `emoji-icons`, `spacing-off-grid`, `horizontal-overflow` |
| `lint/` | `tw-unknown-class`, `tw-v3-arbitrary-var`, `tw-v3-renamed`, `page-level-use-client`, `image-priority-deprecated`, `heavy-import`, `browser-global-in-render`, `timer-in-render`, `outline-none-no-replacement`, `canonical-in-root-layout`, `og-merge-drops-parent`, `not-found-in-suspense`, `dynamic-metadata-streaming`, `sitemap-lastmod-now`, `route-js-budget` |

`void rules --json` prints the full registry (the source of truth; the website renders `/rules/<id>` from it).
