Getting Started

Typography

The type system uses Inter 300 for all UI text and Source Code Pro for monospace content. Both fonts are loaded at the app level via Next.js next/font and exposed as CSS variables that feed into the @theme inline token bridge.

Font families

TokenFontUsage
--font-sansInter 300All body text, labels, headings, UI copy
--font-monoSource Code ProCode blocks, inline code, terminal output, token names

Inter is loaded from Google Fonts via next/font/google at weight 300. Source Code Pro is also loaded from Google Fonts. Both use the variable option to emit CSS custom properties that feed into the Tailwind token bridge. All text uses letter-spacing: 0.001em (0.1% text spacing) for improved readability.

How fonts are loaded

// apps/docs/app/layout.tsx
import { Inter, Source_Code_Pro } from "next/font/google";

const inter = Inter({
  variable: "--font-inter",
  subsets: ["latin"],
  weight: "300",
  display: "swap",
});

const sourceCodePro = Source_Code_Pro({
  variable: "--font-source-code-pro",
  subsets: ["latin"],
  weight: ["400", "500", "600", "700"],
});

// Applied to <body>:
// className={`${inter.variable} ${sourceCodePro.variable} font-sans antialiased`}

The variable option on each font emits a CSS custom property (--font-inter, --font-source-code-pro) scoped to the element the class is applied to. The @theme inline block then maps these to the Tailwind-facing tokens:

/* globals.css @theme inline block */
@theme inline {
  --font-sans: var(--font-inter);
  --font-mono: var(--font-source-code-pro);
}

Using font-sans in any Tailwind class therefore resolves to Inter, and font-mono resolves to Source Code Pro. Each app can point these tokens at different fonts by overriding the mapping in its own globals.css.

Type scale

The design system uses Tailwind's default type scale with no custom additions. The scale is grid-aligned: every size and its corresponding line height are multiples of 4px.

ClassSizeLine heightUsage
text-xs12px16pxCaptions, timestamps, section labels, badge text
text-sm14px20pxUI body text, table cells, nav items, helper text
text-base16px24pxDefault prose body (set on <body>)
text-lg18px28pxLead paragraphs, sub-section titles
text-xl20px28pxSection headings (h3 in UI contexts)
text-2xl24px32pxPage sub-headings (h2)
text-3xl30px36pxPage titles (h1)

Heading examples

These headings are rendered by the prose layer. In UI components, apply the same sizing and weight via Tailwind utilities directly rather than relying on the prose plugin.

Heading 1 — page title

Heading 2 — section

Heading 3 — sub-section

Heading 4 — detail

Prose rendering

Long-form documentation content is wrapped in a prose prose-sm container from the @tailwindcss/typography plugin. The plugin's CSS variables are remapped to our design system tokens in globals.css so that prose content inherits the correct colors automatically.

Prose variableMapped to token
--tw-prose-body--foreground-light
--tw-prose-headings--foreground-default
--tw-prose-lead--foreground-lighter
--tw-prose-links--brand-link
--tw-prose-bold--foreground-default
--tw-prose-bullets--brand-default
--tw-prose-code--brand-300
--tw-prose-pre-bg--background-surface-75
--tw-prose-th-borders--border-default
--tw-prose-td-borders--border-muted

The prose container max-width in this docs site is set to max-w-[57.6rem] with mx-auto centering, matching Supabase's documentation layout proportions.

Code

Use inline code for variable names, file paths, token names, and short snippets. Use fenced code blocks for multi-line examples. Inline code renders with a subtle surface background and border. Block code uses the same surface background with a rounded border and full monospace font.

// Example: cn() utility for class merging
import { cn } from "@repo/ui/utils";

function StatusBadge({
  status,
  className,
}: {
  status: "todo" | "in_progress" | "done" | "blocked";
  className?: string;
}) {
  return (
    <span
      className={cn(
        "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
        {
          "bg-surface-200 text-foreground-lighter": status === "todo",
          "bg-brand-400 text-brand-600": status === "in_progress",
          "bg-brand-500 text-brand": status === "done",
          "bg-destructive-300 text-destructive-600": status === "blocked",
        },
        className
      )}
    >
      {statusLabel[status]}
    </span>
  );
}