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.txtnpx 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 doctorllms.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.