foundations
Star
Setup

Coding agents

A new agent usually writes text-sm text-muted-foreground until it learns the primitive exists. The package ships a summary for that, plus two checks that catch the gaps the summary misses.

The one line

llms.txt installs alongside dist/. Reference it from whatever file your agent reads at the start of a session:

# CLAUDE.md, AGENTS.md, or your agent's equivalent

@node_modules/@supertype.ai/foundations/llms.txt

npx foundations init prints this line for you to paste. The file it belongs in is yours.

What is in the file

211 lines, in 8 sections: Rules, Which component, Entry points, Props worth knowing, Writing copy, Common mistakes, Surfaces, Docs. Written for an agent seeing the package for the first time, so it leads with the rules and gets to the API after.

## Rules

1. **Never write type styles by hand.** If you are about to write `text-sm`,
   `text-xs`, `font-medium`, `leading-tight`, `text-muted-foreground` or similar
   on a text element, there is a primitive for it. Use that instead.
2. **Retune with CSS variables, not classes.** The package owns its classnames.
   Change a `--text-*` rung, `--heading-weight` or a colour token. Do not
   override the package's utilities.
3. **Import from the right entry point.** `blocks`, `essay`, `mdx`, `seo`, `og`,
   `eslint`, `rehype` and `contrast` are subpaths, not part of the root barrel.
4. **Use colour tokens, never literal colours.** `bg-card`, `text-foreground`,
   `border-border`. No hex values, no `bg-zinc-800`, no `dark:` overrides that
   swap one token for another.
5. **Paint a surface, hand down its ink.** Any element you give a background
   needs `INK_ON_FILL` (a tone fill) or one of `INK_ON_CARD`, `INK_ON_POPOVER`,
   `INK_ON_SIDEBAR` (a tinted one), or the type inside it keeps the page's ink
   and fails contrast silently. Never build one of these class strings
   yourself: Tailwind only generates a class it can read as text, so an
   assembled one styles nothing. For a surface with no constant, spread
   `inkOnSurfaceStyle(token)` into `style`.
6. **Write the words like a person would.** See Writing copy below. It applies
   to every string a reader sees and to the comments you leave behind.

Then a table mapping what you want to the component that does it, the entry point each one ships from, and the props worth knowing. A section on writing copy sets the register for the words themselves, and one on common mistakes covers what goes through silently:

## Common mistakes

- `<p className="text-sm text-muted-foreground">`: use `<TypographyMuted>`.
- `<h2 className="text-lg font-semibold">`: use `<TypographyH2>`, or
  `headingClass()` to pass classes into someone else's component.
- Importing `Card`, `Callout` or `Button` from `@supertype.ai/foundations`. They
  live in `@supertype.ai/foundations/blocks`.
- `<Button variant="destructive">` or `variant="secondary"`: those are tones.
  Write `variant="soft" tone="destructive"`, `variant="solid" tone="secondary"`.
- `<Button size="icon-sm">`: one size ladder. Write `size="sm" icon`.
- `<Badge variant="warning">` or `variant="supertype"`: tones, and misspelled ones.
  Write `variant="outline" tone="warn"` and `variant="solid" tone="brand"`.
- `tone="neutral"`, `tone="foreground"` or `tone="accent"`: all three went when the
  tone lists merged. Write `tone="muted"` for the first two, `tone="primary"` for
  the third.
- An `<a>` or `<Link>` styled by hand to look like a button, and equally
  `render={<a href="/x" />}` or `render={<Link href="/x" />}` on anything that
  takes an `href`. Write `<Button href="/x">`. `Button`, `Badge`, `Card` and
  `TypographyLink` all take `href` and route it through one rule
  (`resolveLink`): a scheme leaves the app and gets `target`/`rel`, a `#hash`
  stays a plain anchor, everything else goes through the router's `Link` and
  keeps the view transition. A hand-passed anchor skips all of that. `render` is
  for an element that is genuinely not a link — and for `RailLink`, which takes a
  router `Link` that way on purpose, since `essay/rail.tsx` has to import without
  Next.
- Exporting a constant from a `"use client"` module and importing it into a server
  component. It crosses the boundary as a stub, not a string, so an `href` built that
  way arrives as a function. Keep shared constants in a plain module.
- `<TypographyMuted tone="default">`: a type error. Use `TypographyP`.
- Binding fonts with `font.className` instead of `font.variable`. The className
  form sets `font-family` on the element and leaves the roles unresolved.
- Omitting `@source '../node_modules/@supertype.ai/foundations/dist/**/*.js'` from
  the CSS entry. Tailwind then purges every class the package ships.
- Omitting `@import "@supertype.ai/foundations/theme.css"`. `tokens.css` names the
  colour roles but holds no values, so the whole palette resolves to nothing.
- Using `Accordion` for a static FAQ. `Disclosure` runs on the browser alone.
- Adding a second `@custom-variant dark`. `tokens.css` already binds it.

Run `npx foundations doctor` in the app to check the last four.

How it stays current

The prose is hand-written, since the guidance is the useful part. Coverage is mechanical: a build-time check walks the real exports of every entry point and fails the build when a name is missing from the file.

yarn build runs the check across all 9 entry points in the exports map and reports the count it covered, so every component that ships appears here. Judging whether the advice still holds stays a human job, so keep the guidance short enough to re-read.

The lint rules

An agent can miss the summary. The same design rules ship as ESLint selectors from @supertype.ai/foundations/eslint, which fail a build where the summary only advises:

// eslint.config.js
import { designConfig } from "@supertype.ai/foundations/eslint";

export default [
  ...designConfig({ accents: "the brand tints", weights: true }),
];

They catch hand-rolled colour (bg-zinc-800, hex literals, dark: overrides of a token), a size class on a primitive that owns its size, and a surface token used as ink. Plain data, no plugin, ESM and CommonJS builds both. See the tooling docs.

doctor in CI

The wiring an agent gets wrong sits outside the JSX. A missing @source line, imports in the wrong order, a font bound with .className: all silent, all invisible to a linter.

- run: npx foundations doctor
Three layers

llms.txt tells the agent what to write. The lint rules catch what it wrote anyway. doctor checks the wiring both of them are blind to, and exits non-zero on a real problem.