Content
@supertype.ai/foundations/blocks
Components that sit inside prose. Tabs are a client component built on Base UI. The rest render on the server.
Every component outside typography and prose. They ship from two import subpaths, and each group names the one it comes from.
@supertype.ai/foundations/blocks
Components that sit inside prose. Tabs are a client component built on Base UI. The rest render on the server.
Pass title and description for the default layout, or compose the slots directly for full control. An href makes the whole card a link; an href with a scheme opens in a new tab.
Two variants. compact is sized to sit inside a panel or sidebar. editorial sets the body at reading size and adds an accent rail, for docs and long-form pages.
Rotate the key first
This deletes the slot
The same two axes as Button, spelled the same way, minus link, which belongs to things you click. Both apps had grown a private list instead, one carrying warning and supertype for tones the package already named warn and brand.
A numbered sequence. Numbers come from a CSS counter, so reordering steps renumbers them automatically, and the digits are excluded from copied text and from the accessibility tree.
#main.npx foundations initto write the imports for you..variable, never .className.The one way to show and hide a row, built on the native <details> element: no JavaScript, correct before hydration, and animated where the browser supports it. Set type to single to make a group single-open, which the browser handles for you.
TabGroup takes the tabs as data, which is the shape to reach for. Its variant picks the box: default renders a boxed segmented track, line drops the surface and underlines the active tab, as in the preview and code switcher on this page. Its tone inks the marker alone, which on line means the underline and the active tab's icon. With no content on any tab it is a picker, the strip alone; size sm is the compact strip for a toolbar or a chart. The values' own type flows to value and onValueChange, so a picker can drive a union of values without a cast.
Bulletin is the panel shell without any copy. It gives you a top accent, an eyebrow, a headline, a short intro, a two-column list, and a footer action. Colophon is the same structure with the package's own message built in. A standalone attribution link variant is also provided for footers that need compactness.
The default panel. This is just <Colophon />. Hover over any of the hues to expand it.
Typography primitives, semantic tokens, the essay shell, and contrast checks in one package, enforced in CI.
Decisions live in one place
Utility classes are for styling. The actual decision belongs in the design system. Every type style, tone, and divider on this page comes from one package, so a change is one diff instead of a search through the app.
Every colour is measured twice
WCAG tells us whether a colour passes the audit. APCA's Lc tells us how it feels in context, since the same contrast can read differently on different backgrounds. Both checks run against the shipped stylesheet in light and dark mode, so CI catches anything that slips.
The compact row for a footer that only has room for one line.
Use children for your own copy. It appears under the rule, where you would usually place a short note about the site or the people behind it.
Typography primitives, semantic tokens, the essay shell, and contrast checks in one package, enforced in CI.
Decisions live in one place
Utility classes are for styling. The actual decision belongs in the design system. Every type style, tone, and divider on this page comes from one package, so a change is one diff instead of a search through the app.
Every colour is measured twice
WCAG tells us whether a colour passes the audit. APCA's Lc tells us how it feels in context, since the same contrast can read differently on different backgrounds. Both checks run against the shipped stylesheet in light and dark mode, so CI catches anything that slips.
Bulletin uses the same structure without the content. It works well for announcements, release notes, or status updates. Every slot is optional.
Two things shipped this week, and one of them changes how you set up a recurring report.
Scheduled exports
Pick a cron schedule and the report lands in your inbox on the hour. We run backfills from the same query, so a resend matches the original.
Faster cold starts
The worker pool now warms up on deploy instead of waiting for the first request. The slowest page in the app dropped from 1.9s to 240ms at the median.
BuiltWithFoundations is the standalone link. Use it when the link sits inside another layout, such as a footer row with other links.
The primitive behind every index in the package: a list with a rule down its side. On RailLink, active highlights an item, nested indents a sub-heading, and render swaps the anchor for a router link. It runs without client hooks, so it works in a server component.
Anchors, with depth
Routes, via render
A margin index that highlights the current section with a scroll spy. Pass sections as { id, label } matching the ids on your headings. label renames the rail heading; pass null to hide it. An empty list renders nothing at all. The examples below spy on this page, so they update as you scroll.
TableOfContents plus a progress donut, for pages read start to finish. Takes TocHeading[], { depth, id, label }, the shape extractHeadings returns. Headings at depth 3 are indented.
A hairline progress indicator fixed to the top of the viewport, for the widths where the rail is hidden. Mount one per page, above your header. It shares a single scroll listener with ReadingRail, so using both costs one subscription. The preview is omitted here, since it would track this page instead of the example.
@supertype.ai/foundations/essay
Layout, meta, and the shell that composes them. Use the individual pieces for MDX articles, and EssayDocument when the content is data.
A three-track grid: margin, measure, and an aside column that usually stays empty. Reserving that third track keeps the prose aligned across pages with and without asides. The measure grows with each step, and the aside appears once the container reaches 72rem. This is a container query rather than a viewport one, so a shell in a narrow column drops the rail and centers the prose instead of leaving a margin that is too narrow for a label. The preview uses this page's column rather than forcing a width it cannot support, so it shows the centered case. The specimen runs it at page width, where the margin track appears.
Use EssayColumns when the margin holds something other than a rail. For articles, ReadingLayout composes these columns with the reading rail already in the aside.
scaled to the 72rem container this block asks for
ReadingLayout combines EssayColumns with a reading rail in the margin for articles whose body is supplied as prose or compiled MDX. It works like EssayLayout, with the same column and sticky offset, but uses a scroll-spied rail instead of the declared-section index. If there are no headings, the rail disappears and the layout stays centered. The preview uses that narrower layout for the same reason: it is given the column rather than a fixed width. Expand the container past 72rem, as in the specimen, and the rail reappears.
Use ReadingLayout when the body arrives as prose or MDX. Pass the headings extracted from the markdown, and the rail, sticky offset, and headingless case are handled for you.
scaled to the 72rem container this block asks for
An ordered list for stages that feed into one another: a pipeline, a method, a sequence of phases. Reach for it over a card grid when the order carries meaning.
extractHeadings walks the raw markdown, so the index is ready before the MDX is compiled.
createSlugger follows the same algorithm as rehype-slug, so rail anchors match the ids on the headings.
ReadingRail takes the list as returned. The depth on each heading is what drives the indent.
The byline row under an article title. PostMetaRow lays out PostDate, ReadTime and TagPills, separated by MetaDot. Dates format in en-US on both server and client, which keeps hydration free of a locale mismatch. Use formatPostDate for the same output outside React, such as in an OG image or a feed.
EssayHeader, EssayLayout, EssaySection, EssayPullQuote and EssayFigure compose an article by hand. EssayDocument takes the sections as data instead and builds the margin index from them. See a rendered essay for what they look like at full page width.
@supertype.ai/foundations/essay
The functions that produce heading data for the rails, and the two hooks they run on.
extractHeadings pulls h2 and h3 from raw markdown, ignoring fenced code. It runs on the string alone, so it works at build time. Ids come from createSlugger, which follows GitHub's algorithm and therefore matches the ids rehype-slug adds to your headings. readingTime skips code blocks, which keeps the estimate honest on technical posts. The output below was generated at build time.
extractHeadings(source)
#the-queue#draining-it#the-measurecreateSlugger()
#the-queue#the-queue-1readingTime(source)
1 min. The fenced block is excluded from the count.
The hooks behind TableOfContents and ReadingRail, exported so you can build your own. useScrollSpy tracks a list of heading ids with a single IntersectionObserver. useReadingProgress returns 0 to 1 from a shared store, so any number of callers on a page share one scroll listener.