Skip to main content

For AI Agents

This page is the canonical entry point for AI assistants (Claude Code, Cursor, Copilot, custom agents) that need to write code against @ngrok/mantle.

TL;DR for the agent

Use the @ngrok/mantle design system. Components live at @ngrok/mantle/<name>(e.g. @ngrok/mantle/button). Import subpaths are flat even though docs URLsgroup components by category (/components/<category>/<name>). All pages havea plain-markdown twin at <page>.md. The full library index is at /llms.txtand a structured component manifest is at /api/components.json. Hooks andutilities have their own manifests at /api/hooks.json and /api/utils.json.Offline, node_modules/@ngrok/mantle/dist/agent.json carries the same recordsfor the installed version, and dist/llms.txt is its one-line index.

Machine-readable entry points

URLPurpose
/llms.txtCurated index of every docs page (title, description, html + markdown URLs). Follows the llms.txt convention.
/llms-full.txtConcatenated plain-markdown of every docs page, then # Framed preview sources — the whole React source of the app-shell and layout demos the pages embed. Drop into a context window when full coverage is needed, and read a preview's source rather than a page's fence when you are building a shell.
/api/components.jsonStructured component manifest. Each entry has name, slug, kind, status, importPath, docsUrl, markdownUrl, a one-line summary, the source jsdoc first sentence, examples (the body of each @example block, which for a compound component is the full part tree), aliases (search words such as modal for Dialog), and the sibling boundary from the source JSDoc: useWhen, and siblings with a name and the boundary that picks it instead. kind: "component" entries also carry category (layouts are a flat family and omit it).
/api/hooks.jsonStructured hooks manifest. Each entry has name, importPath, docsUrl, markdownUrl, an optional summary, and examples from the hook's JSDoc.
/api/utils.jsonStructured utilities manifest. Same shape as hooks.
node_modules/@ngrok/mantle/dist/agent.jsonThe three manifests above in one file, written when the package is packed, so an agent with no network reads the records for the installed version. dist/llms.txt beside it lists every module on one line with its import path and summary, and dist/migrations/ holds the codemods the migration guides name.
/api/package.jsonCurated @ngrok/mantle package info: version, peer/dev deps, full list of importable subpaths.
/api/changelog.jsonPublished changelog parsed into a structured per-version, per-bump shape with PR/commit/author metadata.
/api/search-index.jsonFlat searchable index across components, hooks, and utilities with keyword tokens.
/api/schema.jsonJSON Schema (draft-07) definitions for every endpoint above. Use it to validate payloads.
/changelog.mdThe published @ngrok/mantle CHANGELOG.md as plain markdown.
/migrationsStep-by-step migration guides for new APIs/behaviors. Each is reachable as HTML and as raw markdown (append .md), written to feed directly to coding agents.
/layoutsPublished primitives that own page/viewport structure (App Layout, Centered Layout); more graduate from incubation as their APIs prove out.
/recipesCompositional how-tos that wire multiple primitives together with state, data, or routing. Formerly /blocks; old URLs permanently redirect.
/<page>.mdEvery docs page (e.g. /components/actions/button.md) is also served as plain markdown by appending .md.

Suggested system-prompt snippet

Drop this near the top of an agent's system prompt when working in a mantle codebase:

This project uses @ngrok/mantle, ngrok's React + TypeScript + Tailwinddesign system. Conventions:- Components are imported from @ngrok/mantle/<name>, e.g.  `import { Button } from "@ngrok/mantle/button"`.- Hooks are imported from @ngrok/mantle/hooks, e.g.  `import { useBreakpoint } from "@ngrok/mantle/hooks"`.- Utilities live at their own subpaths, e.g. @ngrok/mantle/cx,  @ngrok/mantle/color.- Use `== null` / `!= null` for nullish checks (covers both `null` and  `undefined`) — never `=== undefined` / `!== undefined`.- Compose class names with `cx` from @ngrok/mantle/cx — never use string  interpolation inside className.- Many components support `asChild` to swap the rendered element while  keeping the styling and behavior. Prefer this over wrapping.- Never render a conditional element immediately before bare text  children — a browser translation engine makes React throw there and  the page goes blank. Wrap the text in a <span>, or pass an icon  through the `icon` prop instead of as a child. Mark code, keys,  filenames, IDs, and passcodes translate="no", and nothing else: the  attribute classifies content and is never the fix for a crash.- The app must wrap children in <ThemeProvider> and <Toaster> at the root  (see /). Add <TooltipProvider> once at the root when using shared tooltip  delay or hover settings.- The app's CSS entry must `@import "@ngrok/mantle/mantle.css"` and then  `@import "@ngrok/mantle/source-all.css"`. Keep both in one CSS file.  Without the second import Tailwind generates none of the classes  mantle's components use. The components render unstyled.- To trim the production stylesheet, add `mantleSourcesPlugin()` from  @ngrok/mantle-vite-plugins next to `tailwindcss()` in vite.config.ts  (see /vite-plugins). The CSS file does not change. Dev scans every  mantle file; the build lists only the files the client bundle contains.- Color scales use Tailwind's 50-950 steps (`text-accent-600`). There is  no 1-12 scale, so `text-accent-11` matches nothing. Text color tokens  are `text-strong`, `text-body`, `text-muted`, and `text-placeholder`.- All external dependencies must be exact-pinned (no `^` or `~`).- Without network access, read node_modules/@ngrok/mantle/dist/agent.json:  every component with its import path, examples, aliases, and sibling  boundaries (useWhen, siblings), plus the hooks and utilities, for the  installed version.- Reference the canonical docs at https://mantle.ngrok.com or fetch  https://mantle.ngrok.com/llms.txt for the full index. When building an  app shell, read the demo sources under `# Framed preview sources` in  https://mantle.ngrok.com/llms-full.txt — the fences on the docs pages  are shorter excerpts of them.

Conventions agents should know

  • Polymorphism via asChild. Most components accept asChild (powered by Slot). Use it to render the component as a different element (for example, render a Button as a react-router Link) without losing the component's styling or behavior.
  • Flat import paths, categorized docs URLs. Components import from @ngrok/mantle/<name> (e.g. @ngrok/mantle/button). The docs URL's category segment (/components/actions/button) never appears in import paths. Hooks live at @ngrok/mantle/hooks (e.g. import { useBreakpoint } from "@ngrok/mantle/hooks"). Utilities live at their own subpaths: @ngrok/mantle/cx, @ngrok/mantle/color, etc.
  • Docs slug ≠ import subpath (sometimes). Most components import from a subpath that matches their docs slug (e.g., Button from @ngrok/mantle/button). A few are exported from a parent's subpath: IconButton from @ngrok/mantle/button, PasswordInput from @ngrok/mantle/input, ProgressBar and ProgressDonut from @ngrok/mantle/progress. The structured manifest at /api/components.json is the source of truth; its importPath field is always correct.
  • cx over string interpolation. Compose class names with cx. It merges Tailwind classes and de-duplicates conflicts.
  • Nullish checks use == null / != null. The codebase prefers these over === undefined / !== undefined, since == null covers both null and undefined.
  • Theme + provider setup. Apps must mount ThemeProvider and Toaster at the root. Add TooltipProvider once when using shared tooltip delay or hover settings. Without ThemeProvider, theme tokens are unstyled and there's a flash of unstyled content.
  • Stylesheet setup. The app's CSS entry imports @ngrok/mantle/mantle.css and then @ngrok/mantle/source-all.css (see Stylesheet). mantle.css declares no @source, and Tailwind 4 skips node_modules when it scans for class names, so without source-all.css Tailwind generates none of the classes mantle's components use. The components render without their styles. An open Dialog shows its content in the page flow with no backdrop. Import both from the same CSS file: Tailwind compiles each CSS entry on its own, so an @source in a separate file adds nothing. To trim the production stylesheet, add mantleSourcesPlugin() from @ngrok/mantle-vite-plugins next to tailwindcss() in vite.config.ts. The CSS file does not change.
  • Phosphor icons by default. Import icons from @phosphor-icons/react/<IconName> (e.g. @phosphor-icons/react/Fire). Custom ngrok icons live at @ngrok/mantle/icons.
  • No as casts in app code. Use proper null checks and type narrowing. Type assertions are restricted to as const and dedicated type guards (see Conventions).
  • Translation-safe markup. Google Translate reparents every text node, so React throws NotFoundError and the page goes blank when it inserts an element before one. Never render a conditional element immediately before bare text children: wrap the text in a <span>, move the element after it, or mount it unconditionally. Pass a Button or Badge icon through the icon prop, never as a child. Mark code, keys, filenames, IDs, and passcodes translate="no"; Code, Kbd, CodeBlock.Code, and OtpInput.Slot already do. The attribute classifies content and is never the fix for a crash: when a shape throws, fix the structure, and keep prose translatable.
  • Exact-pinned versions. When adding a dependency, pin it. No ^ or ~ ranges.
  • Tailwind 4. Custom classes use the design tokens defined in mantle.css. Prefer semantic tokens (text-strong, bg-form, border-accent-600) over raw color names where they exist. Color scales use Tailwind's 50–950 steps (text-accent-600, bg-neutral-100); there is no 1–12 scale, so text-accent-11 matches nothing. The text color tokens are on Typography and the palettes on Colors.

Composition patterns

A few high-leverage compositions agents commonly need:

// Button + react-router Link (polymorphic)import { Button } from "@ngrok/mantle/button";import { Link } from "react-router"; <Button appearance="outlined" intent="neutral" asChild>	<Link to="/dashboard">Dashboard</Link></Button>;
// Dialog + formimport { Dialog } from "@ngrok/mantle/dialog";import { Button } from "@ngrok/mantle/button"; <Dialog.Root>	<Dialog.Trigger asChild>		<Button appearance="outlined" intent="neutral">			Edit		</Button>	</Dialog.Trigger>	<Dialog.Content>		<Dialog.Header>			<Dialog.Title>Edit profile</Dialog.Title>		</Dialog.Header>		{/* form here */}	</Dialog.Content></Dialog.Root>;
// Toast on async actionimport { makeToast, Toast } from "@ngrok/mantle/toast"; async function save() {	try {		await api.save();		makeToast(<Toast.Root intent="success">Saved</Toast.Root>);	} catch (error) {		makeToast(			<Toast.Root intent="danger">				<Toast.Message>Could not save: {String(error)}</Toast.Message>			</Toast.Root>,		);	}}

Accessibility-first defaults

Every mantle component is built on semantic HTML and tested ARIA patterns. Agents should:

  • Render real form controls (Input, Checkbox, RadioGroup); never style a <div> with click handlers as a button.
  • Pair every form control with a Label.
  • Use SkipToMainLink + Main at the top of the document tree.
  • Set accessible names for icon-only buttons via the required label prop on IconButton.
  • See the Accessibility page for a full keyboard / ARIA reference per component.

When to ask vs. when to act

  • If the user asks for "a primary button," generate a Button with appearance="filled" intent="neutral", the system's default primary action. Button and IconButton require explicit appearance and intent; there are no defaults. The two take different tone unions:
    • Button takes "accent", "danger", or "neutral". Use intent="neutral" when no tone is implied. Reserve intent="accent" for deliberate brand emphasis: an action that should carry the accent color.
    • IconButton takes "neutral" alone. Reach for Button where the tone carries meaning.
  • If choosing between Tooltip, Popover, and HoverCard, follow the JSDoc guidance: Tooltip for short label hints, Popover for interactive content, HoverCard for non-essential preview cards.
  • If choosing between DataTable and Table: DataTable for dynamic, sortable, filterable, paginated tabular data; Table for static layout.
  • When uncertain, prefer to fetch the relevant /<page>.md and read the full doc rather than guessing.

Versioning

@ngrok/mantle is 0.x, so stock semver does not apply. There is no major: a breaking change ships as a minor, and an additive or internal change ships as a patch. Read the Changelog before a minor bump, and run the matching migration guide when one exists. When working in a project, check package.json for the pinned version and prefer the docs at the matching tag on GitHub if you need to pin behavior to that version.