Skip to content

Getting started for developers

You're a developer. Your output is code that consumes Matter's design system — typically inside apps/app, apps/web, apps/docs, or a downstream Matter consumer. This page is the five-minute onboarding.

1. Install

bun add @matter/tokens @matter/components @matter/theme @matter/icons

Or, if you're consuming through the brand layer (marketing and brand surfaces):

bun add @repo/brand

The brand package re-exports everything from @matter/components plus the brand-specific primitives (BetaPill, MatterButton, StatusPill, and friends).

2. Wire the stylesheet

The single canonical stylesheet:

// app/layout.tsx or wherever your root layout lives
import "@repo/brand/styles";

This pulls the chain — @matter/tokens CSS variables, Tailwind v4 @theme inline mapping, motion recipes, the V1 → V2 transitional bridge. One import; never re-order it manually.

3. Import your first component

import { Pill, Button } from "@matter/components";

export function Hello() {
  return (
    <div>
      <Pill tone="green">Live</Pill>
      <Button variant="primary">Save</Button>
    </div>
  );
}

Every export is documented at /components. The props table on each page is generated from the TypeScript source — what you see there is exactly what the IDE will show in autocomplete.

4. Read tokens

Three patterns in order of preference:

// 1. CSS variable — always-correct, theme-aware, zero JS cost.
<div style={{ color: "var(--fg-muted)" }} />

// 2. Tailwind utility — composed through the @matter/presets-tailwind preset.
<div className="text-fg-muted" />

// 3. TypeScript token export — only for computations, not runtime styling.
import { fgMuted } from "@matter/tokens";
const lightVariant = lighten(fgMuted, 0.1);

Never inline a hex. The drift gate fails on raw color literals in MDX, and the same rule applies in your source.

5. Theme, density, forced colors

The site supports four conditions and every component renders correctly under all of them:

ConditionWhere it switchesHow to test
Light theme<html data-theme="light"> (default)Default page render
Dark theme<html data-theme="dark">Toggle from the site header chrome
Compact density<html data-density="compact">Toggle from the header next to theme
Forced colors@media (forced-colors: active)macOS: System Settings → Display → Increase Contrast

Your code never branches on theme — every component reads tokens that adapt automatically.

6. Graceful degradation

Matter follows the next-forge graceful-degradation pattern. Every optional dependency uses optional chaining; missing env vars silently disable features.

// Right — optional chaining
const prices = stripe?.prices.list();

// Wrong — assumes Stripe is configured
const prices = stripe.prices.list();

Every package has a keys.ts that validates required env vars via Zod. The keys() function is called at the consumer's boot time, not at import time, so missing-key errors surface where they're used. Read the next-forge documentation for the broader pattern.

7. The consumer migration playbook

If you're migrating an existing surface from V1 → V2 components:

  1. MonoChip → Pill. Identical visual, swap the import. Tones map 1:1.
  2. CodeCard → CodeBlock with header slot. Same geometry; the header gets <EndpointBadge> + a request-time span.
  3. VerbPill → EndpointBadge in verb-only mode. <EndpointBadge method="POST" /> with no path produces the same chip.
  4. DisplayHeading (V1) → DisplayHeading (V2). Import path changes; visual identity unchanged.

The V1 alias chain in shadcn-bridge.css keeps both versions rendering identically until your consumer fully migrates.

8. Local development

The design site itself runs at localhost:3006:

bun dev --filter design

Every change to packages/tokens, packages/components, packages/brand, or apps/design/content/design/ regenerates the agent surfaces and rebuilds. The drift gate runs on bun run --filter design check:drift — green CI means nothing fell out of sync.

9. When you're stuck

  • Need a component that doesn't exist? Propose it via Governance — the bar is "appears in ≥ 3 real screens" or "fundamental primitive missing."
  • Need a token that doesn't exist? Same path — proposal first, never inline.
  • Component renders wrong? Check the drift gate output, then bun run --filter design typecheck. 90% of "renders wrong" is a stale token cache.
  • Migrating a V1 consumer? Reach for the Authoring rules migration section first.

On this page