This page is the canonical entry point for AI assistants — Claude Code, Cursor, Copilot, custom agents — that need to write code against @ngrok/mantle.
1Use the @ngrok/mantle design system. Components live at @ngrok/mantle/<name>2(e.g. @ngrok/mantle/button). All pages have a plain-markdown twin at3<page>.md. The full library index is at /llms.txt and a structured4component manifest is at /api/components.json. Hooks and utilities have5their own manifests at /api/hooks.json and /api/utils.json.Drop this near the top of an agent's system prompt when working in a mantle codebase:
1This project uses @ngrok/mantle, ngrok's React + TypeScript + Tailwind2design system. Conventions:34- Components are imported from @ngrok/mantle/<name>, e.g.5 `import { Button } from "@ngrok/mantle/button"`.6- Hooks are imported from @ngrok/mantle/hooks, e.g.7 `import { useBreakpoint } from "@ngrok/mantle/hooks"`.8- Utilities live at their own subpaths, e.g. @ngrok/mantle/cx,9 @ngrok/mantle/color.10- Use `== null` / `!= null` for nullish checks (covers both `null` and11 `undefined`) — never `=== undefined` / `!== undefined`.12- Compose class names with `cx` from @ngrok/mantle/cx — never use string13 interpolation inside className.14- Many components support `asChild` to swap the rendered element while15 keeping the styling and behavior. Prefer this over wrapping.16- The app must wrap children in <ThemeProvider> and <Toaster> at the root17 (see /). Add <TooltipProvider> once at the root when using shared tooltip18 delay or hover settings.19- All external dependencies must be exact-pinned (no `^` or `~`).20- Reference the canonical docs at https://mantle.ngrok.com or fetch21 https://mantle.ngrok.com/llms.txt for the full index.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.@ngrok/mantle/<name> (e.g. @ngrok/mantle/button). 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.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.== null / != null. The codebase prefers these over === undefined / !== undefined, since == null covers both null and undefined.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.@phosphor-icons/react/<IconName> (e.g. @phosphor-icons/react/Fire). Custom ngrok icons live at @ngrok/mantle/icons.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).^ or ~ ranges.mantle.css. Prefer semantic tokens (text-strong, bg-form, border-accent-600) over raw color names where they exist.A few high-leverage compositions agents commonly need:
1// Button + react-router Link (polymorphic)2import { Button } from "@ngrok/mantle/button";3import { Link } from "react-router";4 5<Button asChild>6 <Link to="/dashboard">Dashboard</Link>7</Button>;1// Dialog + form2import { Dialog } from "@ngrok/mantle/dialog";3import { Button } from "@ngrok/mantle/button";4 5<Dialog.Root>6 <Dialog.Trigger asChild>7 <Button>Edit</Button>8 </Dialog.Trigger>9 <Dialog.Content>10 <Dialog.Header>11 <Dialog.Title>Edit profile</Dialog.Title>12 </Dialog.Header>13 {/* form here */}14 </Dialog.Content>15</Dialog.Root>;1// Toast on async action2import { makeToast, Toast } from "@ngrok/mantle/toast";3 4async function save() {5 try {6 await api.save();7 makeToast(<Toast.Root priority="success">Saved</Toast.Root>);8 } catch (error) {9 makeToast(10 <Toast.Root priority="danger">11 <Toast.Message>Could not save</Toast.Message>12 <Toast.Description>{String(error)}</Toast.Description>13 </Toast.Root>,14 );15 }16}Every mantle component is built on semantic HTML and tested ARIA patterns. Agents should:
Input, Checkbox, RadioGroup) — never style a <div> with click handlers as a button.Label.SkipToMainLink + Main at the top of the document tree.aria-label on IconButton.Button with appearance="filled" priority="default". The library's defaults should be safe.Tooltip, Popover, and HoverCard, follow the JSDoc guidance — Tooltip for short label hints, Popover for interactive content, HoverCard for non-essential preview cards.DataTable and Table: DataTable for dynamic, sortable, filterable, paginated tabular data; Table for static layout./<page>.md and read the full doc rather than guessing.@ngrok/mantle follows semver. Breaking changes are documented in the Changelog. 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.