Skip to content

Agents

Matter's design system ships three agent-readable surfaces alongside the human-readable site. Every surface is fetched by URL; every surface is versioned; every surface is regenerated on every build from the canonical sources (@matter/tokens, @matter/components, the MDX corpus). This page is the canonical guide for wiring an agent to consume them.

The reader is either an AI agent fetching this page as context, or a developer building an agent that consumes Matter's design system. Both audiences are served by the same content.

The three surfaces

SurfaceURLWhen to fetch
Structured manifest/design-system.jsonWhen the agent needs structured token / component metadata
JSON Schema/design-system.schema.jsonOnce at agent boot, for validation
Flat-text bundle/llms-design.txtWhen the agent needs full corpus context (system-prompt injection)
Per-page raw MDX/api/raw/<slug>When the agent needs one page's details without the full bundle

All four are CORS-enabled and cached for 1 hour.

Quick start (5 lines)

const manifest = await fetch("https://design.mattermode.com/design-system.json").then((r) => r.json());
const version = manifest.version;                       //"2.3.0"
const tokens = manifest.tokens;                         // grouped by kind
const components = manifest.components;                 // canonical export list
const button = components.find((c) => c.name === "Button");

Everything else is detail on top of these five lines.

Manifest shape

The top-level keys of design-system.json:

KeyTypeUse
$schemaURLPoints to /design-system.schema.json
versionstringpackages/tokens/package.json#version. Pin against this.
generated_atISO timestampDeterministic from git HEAD's commit time
site_urlURLhttps://design.mattermode.com
tokensobjectKeyed by kind: colors, radii, shadows, motion, layout, spacing, typography, density
componentsarrayEvery exported component with name, import_path, source_url, page_url, has_page
pagesarrayEvery MDX page indexed by slug
recipesarrayEvery recipe page (the "real screens")
patternsarrayEvery pattern page (composition recipes)
motion_grammarobjectdurations + easings extracted from the motion tokens
voiceobjectrules[] — canonical voice constraints
accessibilityobjectcontract (always "AA"), focus_ring, reduced_motion, forced_colors

Every token row carries:

{
  name: string;        // "fg" / "peach2" / "radius-md"
  css_var: string;     // "--fg" / "--peach-2" / "--radius-md"
  value: string;       // "#0D0D0D" / "12px" / "0 1px 2px rgba(0,0,0,0.05)"
  source: "matter" | "brand";
  ts_path: string;     // "packages/tokens/src/index.ts#colors.fg"
  group?: string;      // "Foreground" / "Surface" / "Status" — taxonomy
  doc?: string;        // optional inline doc string from the TS source
}

Every component row carries:

{
  name: string;                    // "Button"
  import_path: "@matter/components" | "@repo/brand/components";
  source_url: string;              // GitHub blob URL
  source_path: string;             // relative path in the monorepo
  has_page: boolean;
  page_url?: string;               // /components/button when has_page
}

Validate before consuming

Schema drift is the agent's leading failure mode. Validate the manifest against the JSON Schema on every fetch:

import Ajv from "ajv";
import schema from "./design-system.schema.json";

const ajv = new Ajv({ strict: true });
const validate = ajv.compile(schema);

const manifest = await fetch(MANIFEST_URL).then((r) => r.json());
if (!validate(manifest)) {
  throw new Error(`Schema drift: ${ajv.errorsText(validate.errors)}`);
}

If your agent caches the manifest, validate the cached payload too — schema drift can happen between agent runs.

Pin against the version

The version field is the canonical pin point. Three patterns:

// Pattern 1 — exact pin. Strict but brittle; agent breaks on every release.
if (manifest.version !== "2.3.0") throw new Error("Bumped");

// Pattern 2 — minor pin via semver. Tolerates patches; surfaces minor + major.
import semver from "semver";
if (!semver.satisfies(manifest.version, "~2.3.0")) throw new Error("Bumped beyond patch");

// Pattern 3 — drift detection. Allow any version, but log when it changes.
if (manifest.version !== lastSeenVersion) {
  logger.info("Design system bumped", { from: lastSeenVersion, to: manifest.version });
  lastSeenVersion = manifest.version;
}

Choose by tolerance for design-system drift in your agent's output.

llms-design.txt for system prompts

When the agent needs the full corpus as system-prompt context:

curl -s https://design.mattermode.com/llms-design.txt

The flat-text bundle is every MDX page concatenated, capped at ~150 KB. Inject it once into the system prompt at agent boot; cache for 1 hour. The bundle is regenerated on every build via build-llms-design.ts.

For Claude / GPT / Gemini system-prompt patterns:

const designSystemContext = await fetch("https://design.mattermode.com/llms-design.txt").then((r) => r.text());

const systemPrompt = `
You are an agent generating UI code for Matter consumers. The Matter design
system is documented below. Generate code that consumes the documented
components and tokens; never invent tokens or components that don't appear
in the manifest.

${designSystemContext}

When responding, cite the component you used by its name and link (e.g.
"Used <Pill> from /components/pill").
`;

Per-page raw MDX

When the agent needs a single page's details and you don't want 150 KB of context:

curl -s https://design.mattermode.com/api/raw/components/button

Returns the raw MDX source as text/markdown. Use this for targeted lookups — the agent has already decided which component is relevant and now wants the full docs for it.

Worked examples

Generate a Matter-branded React component

async function generateComponent(userRequest: string) {
  const manifest = await getManifest();
  const candidates = manifest.components.map((c) => `${c.name} (${c.import_path})`);

  const result = await claude.messages.create({
    system: `${await getDesignSystemContext()}\n\nGenerate code consuming only the listed components.`,
    messages: [
      { role: "user", content: `Build me: ${userRequest}\n\nAvailable components: ${candidates.join(", ")}` },
    ],
  });

  return result.content;
}

Score a generated screen against the system

function scoreAgainstSystem(generatedSource: string, manifest: Manifest) {
  const componentNames = new Set(manifest.components.map((c) => c.name));
  const tokenVars = new Set(manifest.tokens.colors.map((t) => t.css_var));

  const issues: string[] = [];

  // Every import from @matter/components or @repo/brand should be in the manifest.
  const imports = parseImports(generatedSource);
  for (const imp of imports) {
    if (imp.from.startsWith("@matter") || imp.from.startsWith("@repo/brand")) {
      for (const name of imp.names) {
        if (!componentNames.has(name)) {
          issues.push(`Unknown component: ${name} from ${imp.from}`);
        }
      }
    }
  }

  // Every CSS var should be in the token surface.
  const usedVars = extractCssVars(generatedSource);
  for (const v of usedVars) {
    if (!tokenVars.has(v)) {
      issues.push(`Unknown CSS variable: ${v}`);
    }
  }

  // No raw hex literals.
  const hex = generatedSource.match(/#[0-9a-fA-F]{3,8}\b/g);
  if (hex) {
    issues.push(`Raw hex literals: ${hex.slice(0, 3).join(", ")}…`);
  }

  return issues;
}

Detect drift between cached and live

function getCachedManifest(): Manifest | null {
  try {
    return JSON.parse(localStorage.getItem("ds-manifest") ?? "");
  } catch {
    return null;
  }
}

async function refreshIfDrifted() {
  const cached = getCachedManifest();
  const liveHead = await fetch("https://design.mattermode.com/design-system.json", { method: "HEAD" });
  const liveEtag = liveHead.headers.get("etag");

  if (cached?.etag === liveEtag) return cached;

  const live = await fetch("https://design.mattermode.com/design-system.json").then((r) => r.json());
  localStorage.setItem("ds-manifest", JSON.stringify({ ...live, etag: liveEtag }));
  return live;
}

Cite component import paths in generated code

function withCitation(component: { name: string; import_path: string; page_url?: string }) {
  return `Used <${component.name}> from ${component.import_path}${component.page_url ? ` (${component.page_url})` : ""}.`;
}

The voice rule (must propagate to every agent)

The single rule every agent must honour when generating Matter copy:

Never compare Matter to other products. No "Matter's analog of X," no "deliberate deviation from Y," no "mirrors Z's grammar." Describe what Matter does plainly and self-contained. Factual references to standards (WAI-ARIA roles, RFC 7807, OAuth 2.1, MCP) are fine; framing Matter's design as a comparison to a named competitor product is not.

This rule lives in manifest.voice.rules[] so the agent can quote it verbatim:

const voiceRules = manifest.voice.rules.join("\n");
const systemPrompt = `${corpusContext}\n\nWhen writing copy, follow:\n${voiceRules}`;

What the manifest doesn't have

  • Rendered visuals. The agent surface is structured; for pixel-perfect renders, fetch a ComponentPreview screenshot via the per-page route (when shipped).
  • Compiled CSS. Tokens surface as values, not as compiled rules. If you need compiled CSS, fetch the design site's CSS directly (not stable; subject to bundler changes).
  • Figma node IDs. Component MDX carries <FigmaLink node="…"> blocks — fetch the per-page raw MDX to extract these.

When the manifest doesn't cover something

The manifest is the canonical structured surface. When something isn't there:

  1. The page doesn't exist. Propose it via Governance — open a PR with the missing page.
  2. The data shape isn't structured yet. File an issue tagged agent-payload describing what you need.

Don't synthesise design-system data. Don't invent tokens or components. If your agent is reaching for something not in the manifest, that's a signal — either the design system needs to grow, or the agent's task is outside the design system's surface.

Versioning contract

BumpLikely impact on the agent
Patch (2.3.0 → 2.3.1)No agent action; cached manifests stay valid.
Minor (2.3.0 → 2.4.0)Re-fetch and re-validate; new tokens / components may be available. Existing references stay valid.
Major (2.x → 3.0)Re-fetch, re-validate, re-read the changelog. Existing references may be deprecated or removed.

The changelog page is the agent's source of truth for "what changed between versions." Treat a major bump as a prompt-template review trigger.

Endpoints reference

URLFormatCacheUse
/design-system.jsonJSON1hStructured manifest
/design-system.schema.jsonJSON Schema1hValidation
/llms-design.txtText1hSystem-prompt context
/llms.txtText1hIndex of pages in the bundle
/api/raw/<slug>MarkdownNoneSingle-page raw MDX
/changelog (HTML)HTMLLiveHuman-readable release notes

Everything is CORS-enabled. No authentication required. Rate-limited per-IP at the Vercel edge.

On this page