# @ngrok/mantle — Full Documentation
> Concatenated markdown for every page on https://mantle.ngrok.com. Each section is preceded by its canonical docs URL. JSX preview blocks (``) are dropped; code fences are preserved verbatim.
Docs index: https://mantle.ngrok.com/llms.txt
Component manifest: https://mantle.ngrok.com/api/components.json
Hooks manifest: https://mantle.ngrok.com/api/hooks.json
Utilities manifest: https://mantle.ngrok.com/api/utils.json
---
---
title: Accessibility
description: How mantle approaches accessibility, the keyboard and ARIA contracts each component honors, and how to build accessible UIs on top of it.
---
# Accessibility
Mantle is built so the accessible thing and the easy thing are the same thing. Components render real semantic HTML, lean on battle-tested primitives ([Radix](https://www.radix-ui.com), [Ariakit](https://ariakit.org), [Headless UI](https://headlessui.com)) for complex interaction patterns, and ship with the keyboard, focus, and ARIA behavior already wired up. This page collects the cross-cutting guidance that doesn't fit on any single component page.
## Principles
- **Semantic HTML first.** A [`Button`](/components/button) is a `
And this icon renders at the end:{" "}
} iconPlacement="end">
ngrok dashboard
!
```
## API Reference
### Anchor
All props from [a](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes), plus:
| Prop | Type | Default | Description |
| ---------------- | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------- |
| `icon?` | `ReactNode` | | An icon to render inside the anchor. |
| `iconPlacement?` | `"start"` \| `"end"` | `"start"` | The side that the icon will render on, if one is present. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Anchor` styling onto alternative element types or your own React components. |
---
---
title: Badge
description: A non-interactive label used to highlight short, scannable information — a status, a category tag, or a count — in the smallest possible footprint.
---
# Badge
A non-interactive label used to highlight short, scannable information — a status, a category tag, or a count — in the smallest possible footprint.
## When to use
- Status indicators: `Succeeded`, `Failed`, `Pending`, `Beta`.
- Category or tag chips alongside list items, table rows, or cards.
- Counts (e.g. `12 new`) when paired with brief context.
## When not to use
- For interactive UI. Badges are not buttons or links — use [`Button`](/components/button) or [`Anchor`](/components/anchor) (optionally with `asChild` styling) instead.
- For long-form text. Keep labels to one or two short words.
- As the sole signal of meaning. Pair color with a label or icon so the distinction works without color (color blindness, monochrome themes).
## Choosing a color
Prefer functional colors (`success`, `warning`, `danger`, `info`, `accent`, `neutral`) for status meaning so theming stays coherent. Reach for named hues only when the badge's semantic role isn't already covered.
```tsx
import { Badge } from "@ngrok/mantle/badge";
import { GlobeHemisphereWestIcon } from "@phosphor-icons/react/GlobeHemisphereWest";
Muted neutral
}>
Muted neutral
```
## Polymorphism
When you want to render *something else* as a `Badge`, you can use the `asChild` prop to compose. This is useful when you want to splat the `Badge` styling onto a `react-router` `Link`.
```tsx
import { Badge } from "@ngrok/mantle/badge";
import { GlobeHemisphereWestIcon } from "@phosphor-icons/react/GlobeHemisphereWest";
import { Link, href } from "react-router";
}>
See our colors!
;
```
## API Reference
### Badge
All props from [span](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span#attributes), plus:
| Prop | Type | Default | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ |
| `appearance` | `"muted"` | | Defines the visual style of the `Badge`. Currently only supports the `muted` variant. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Badge` styling onto alternative element types or your own React components. |
| `color?` | `"neutral"` \| `"danger"` \| `"important"` \| `"info"` \| `"success"` \| `"warning"` \| `"blue"` \| `"cyan"` \| `"fuchsia"` \| `"gray"` \| `"green"` \| `"indigo"` \| `"lime"` \| `"orange"` \| `"pink"` \| `"purple"` \| `"red"` \| `"teal"` \| `"yellow"` | `"neutral"` | The color variant of the `Badge`. Supports all [named colors](/base/colors), both functional and from the palette. |
| `icon?` | `ReactNode` | | An icon to render inside the badge. Will be automatically sized for you. |
---
---
title: BrowserOnly
description: A wrapper component that ensures its children only render in the browser, after hydration has completed.
---
# BrowserOnly
A wrapper component that ensures its children only render in the browser, after hydration has completed. Useful for components that rely on browser-only APIs like `window`, `document`, `localStorage`, or media queries.
```tsx
import { BrowserOnly } from "@ngrok/mantle/browser-only";
}>
{() =>
This only renders in the browser after hydration!
}
Loading...}>
{() => (
Browser-only content with window dimensions:
Width: {window.innerWidth}px
Height: {window.innerHeight}px
)}
```
## API Reference
### BrowserOnly
| Prop | Type | Default | Description |
| ----------- | ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `() => ReactNode` | | Children must be a render function so that evaluation is deferred until after hydration has occurred. |
| `fallback?` | `ReactNode` | `null` | Optional fallback to render on the server (and during hydration) before the client-only children are mounted. Ideally, this should be the same dimensions as the eventual children to avoid layout shift. |
---
---
title: Button
description: Initiates an action, such as completing a task or submitting information.
---
# Button
Initiates an action, such as completing a task or submitting information.
```tsx
import { Button } from "@ngrok/mantle/button";
OutlinedFilledGhostLinkOutlinedFilledGhostLinkOutlinedFilledGhostLink
```
## Icon and Positioning
Use the `icon` prop to add an icon to the button. By default, it will render on the logical start side of the button. Use the `iconPlacement` prop to change the side the icon is rendered on.
```tsx
import { Button } from "@ngrok/mantle/button";
import { FireIcon } from "@phosphor-icons/react/Fire";
}>Icon Start
} iconPlacement="end">
Icon End
```
## isLoading
`isLoading` determines whether or not the button is in a loading state, default `false`. Setting `isLoading` will replace any `icon` with a spinner, or add one if an icon wasn't given. It will also disable user interaction with the button and set `aria-disabled`.
```tsx
import { Button } from "@ngrok/mantle/button";
import { FireIcon } from "@phosphor-icons/react/Fire";
No Icon + Idle}>Icon Start + Idle
} iconPlacement="end">
Icon End + Idle
No Icon + isLoading} isLoading>
Icon Start + isLoading
} iconPlacement="end" isLoading>
Icon End + isLoading
```
## Polymorphism
When you want to render *something else* as a `Button`, you can use the `asChild` prop to compose. This is useful when you want to splat the `Button` styling onto a `react-router` `Link`. Keep in mind that when you use `asChild` the `type` prop will **NOT** be passed to the child component.
```tsx
import { Button } from "@ngrok/mantle/button";
import { FireIcon } from "@phosphor-icons/react/Fire";
import { Link, href } from "react-router";
} asChild>
See our colors!
;
```
## API Reference
### Button
All props from [button](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes), plus:
| Prop | Type | Default | Description |
| ---------------- | --------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appearance?` | `"ghost"` \| `"filled"` \| `"outlined"` \| `"link"` | `"outlined"` | Defines the visual style of the `Button`. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Button` styling onto alternative element types or your own React components. |
| `icon?` | `ReactNode` | | An icon to render inside the button. When `isLoading` is `true`, the icon will automatically be replaced with a spinner. |
| `iconPlacement?` | `"start"` \| `"end"` | `"start"` | The side that the icon will render on, if one is present. When `isLoading` is `true`, the loading spinner will also render on this side. |
| `isLoading?` | `boolean` | `false` | Determines whether or not the button is in a loading state. Setting `isLoading` will replace any `icon` with a spinner, or add one if an icon wasn't given. It will also disable user interaction with the button and set `aria-disabled`. |
| `priority?` | `"default"` \| `"danger"` \| `"neutral"` | `"default"` | Indicates the importance or impact level of the button, affecting its color and styling to communicate its purpose to the user. |
| `type` | `"button"` \| `"reset"` \| `"submit"` | | The default behavior of the `Button`. Unlike the native `button` element, unless you use the `asChild` prop, **this prop is required and has no default value**. See [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#type) for more information. |
---
---
title: Card
description: A container used to display content in a box, resembling a physical card.
---
# Card
A container used to display content in a box, resembling a physical card. Composed of several sub-components.
```tsx
import { Card } from "@ngrok/mantle/card";
Laborum in aute officia adipisicing elit velit.
Card Title Here
Laborum in aute officia adipisicing elit velit.
Card footer
Card Title Here
Laborum in aute officia adipisicing elit velit.
Laborum in aute officia adipisicing elit velit.
Card footer
```
## Composition
Compose the parts of a `Card` together to build your own:
```text showLineNumbers=false
Card.Root
├── Card.Header
│ └── Card.Title
├── Card.Body
└── Card.Footer
```
## API Reference
### Card.Root
A container that displays content in a box resembling a physical card. The root component of all `Card` sub-components.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Card` styling onto alternative element types or your own React components. |
### Card.Body
The main content of a card. Usually composed as a direct child of `Card.Root`.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Card.Body` styling onto alternative element types or your own React components. |
### Card.Footer
The footer container of a card. Usually composed as a direct child of `Card.Root`.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Card.Footer` styling onto alternative element types or your own React components. |
### Card.Header
The header container of a card. Usually composed as a direct child of `Card.Root`.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Card.Header` styling onto alternative element types or your own React components. |
### Card.Title
The title of a card. Usually composed as a direct child of `Card.Header`. Renders as an `h3` element by default, but can be changed to any other element by using the `asChild` prop. Prefer using a heading element (`h1-h6`) for accessibility.
All props from [h1-h6](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Card.Title` styling onto alternative element types or your own React components. |
---
---
title: Checkbox
description: A form control that allows the user to toggle between checked and not checked. Supports indeterminate state.
---
# Checkbox
A form control that allows the user to toggle between checked and not checked. Supports indeterminate state.
```tsx
import { Checkbox } from "@ngrok/mantle/checkbox";
import { Label } from "@ngrok/mantle/label";
```
## Form Validation Example
The `Checkbox` can be used in forms with client-side validation. Here's an example using `@tanstack/react-form` and `zod`:
```tsx
import { Button } from "@ngrok/mantle/button";
import { Label } from "@ngrok/mantle/label";
import { Checkbox } from "@ngrok/mantle/checkbox";
import { useForm } from "@tanstack/react-form";
import { z } from "zod";
const formSchema = z.object({
acceptedTermsAndConditions: z
.boolean()
.refine((value) => value, "You must accept the terms and conditions."),
});
function FormExample() {
const form = useForm({
defaultValues: {
acceptedTermsAndConditions: false,
},
validators: {
onSubmit: formSchema,
},
onSubmit: ({ value }) => {
// Handle form submission here
},
});
return (
state.isDirty}>
{(isDirty) => (
Submit
)}
);
}
```
## API Reference
### Checkbox
All props from [input\[type="checkbox"\]](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox), plus:
| Prop | Type | Default | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `checked?` | `boolean` \| `"indeterminate"` | | Whether the checkbox is checked or not. Setting this to `"indeterminate"` will show the indeterminate state. This is useful for parent-child relationships, but requires manual, controlled state. |
| `defaultChecked?` | `boolean` \| `"indeterminate"` | | The checked state of the checkbox when it is initially rendered. Use when you do not need to control its checked state. |
| `validation?` | `"error"` \| `"success"` \| `"warning"` \| `false` \| `() => "error" \| "success" \| "warning" \| false` | | Use the `validation` prop to show a specific validation status. This will change the border and outline of the checkbox. The `false` type is useful with short-circuiting logic. Setting `validation` to `"error"` also sets `aria-invalid`. |
---
---
title: Code
description: Marks a short fragment of inline computer code — a function name, a variable, a CLI flag, a key.
---
# Code
Marks a short fragment of inline computer code — a function name, a variable, a CLI flag, a key. Renders a native `` element with mantle's monospace styling.
## When to use
- Inline within prose to identify code, file paths, env vars, or keys.
- Wrap technical terms that should visually stand apart from running text.
## When not to use
- For multi-line or syntax-highlighted blocks. Use [`CodeBlock`](/components/code-block) instead.
- For keyboard shortcuts. Use [`Kbd`](/components/kbd).
- For arbitrary monospace text that isn't code (use a plain monospace utility class).
```tsx
import { Code } from "@ngrok/mantle/code";
Use the console.log() function to debug your code.
;
```
## Polymorphism
Pass `asChild` to render `Code` styling on a different element — for example, a link wrapping a code-styled label.
```tsx
import { Anchor } from "@ngrok/mantle/anchor";
import { Code } from "@ngrok/mantle/code";
/api/components.json;
```
## API Reference
### Code
All props from [code](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code#attributes), plus:
| Prop | Type | Default | Description |
| ------------ | ----------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `Code` styling onto alternative element types or your own React components. |
| `children` | `ReactNode` | | The content to be rendered inside the inline code element. |
| `className?` | `string` | | Additional CSS classes to apply to the inline code element. |
---
---
title: Code Block
description: Code blocks render and apply syntax highlighting to blocks of code using Shiki at build time.
---
# Code Block
Code blocks render and apply syntax highlighting to blocks of code. Syntax highlighting is performed at build time using [Shiki](https://shiki.style/) via the `mantleCodeBlockPlugins()` Vite plugin, so there is zero highlighting cost in the browser.
Use `mantleCode("language")` tagged template literals to define code values. The Vite plugin transforms these at build time, inlining pre-rendered HTML.
```tsx
import { CodeBlock, mantleCode } from "@ngrok/mantle/code-block";
…;
```
## Examples
### Single Line with a Header
Many code blocks will be single line command line prompts and should be able to render with a header and copy button. This makes it absolutely clear that this example is a command line prompt and not a code sample.
```tsx
Command Line
```
### Horizontal Scrolling
This example is included to demonstrate that code blocks can scroll horizontally if the content is too wide. Mantle attempts to normalize scrollbar styling across browsers and platforms.
```tsx
ngrok-example.js {
res.writeHead(200);
res.end("Hello!");
setTimeout(() => {
Promise.resolve().then(() => {
console.log("url:", server.tunnel.url());
});
}, timeout);
});
// Consumes authtoken from env automatically
ngrok.listen(server).then(() => {
console.log("url:", server.tunnel.url());
});
// really long line here that should wrap around and stuff Officia ipsum sint eu labore esse deserunt aliqua quis irure.
`}
/>
```
### No Header or Copy Button
This is the most simple example of our code block component. While very useful, the copy button is optional. It is also perfectly acceptable to render a code block without a header, especially if context is provided in the surrounding content or the code block is self-explanatory eg. "In your index.js file, paste the following:".
```tsx
{
res.writeHead(200);
res.end("Hello!");
});
ngrok.listen(server).then(() => {
console.log("url:", server.tunnel.url());
});
`}
/>
```
### Single Line with Horizontal Scrolling
This example is included to show the interaction between the copy button and horizontal scrolling on a single verbose terminal command.
```tsx
```
### Server-Rendered Syntax Highlighting
The `CodeBlock` supports server-rendered syntax highlighting for dynamic or user-provided code. Use `createMantleCodeBlockValue()` to construct a value from server-highlighted HTML and pass it to `CodeBlock.Code`. This is useful when the code to highlight isn't known at build time — for example, user input or API responses.
```tsx
import {
CodeBlock,
createMantleCodeBlockValue,
type MantleCodeBlockValue,
} from "@ngrok/mantle/code-block";
// Fetch highlighted HTML from your server
const response = await fetch("/api/shiki-highlight", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, language: "typescript", showLineNumbers: true }),
});
const data = await response.json();
// Create a MantleCodeBlockValue from the server response
const value: MantleCodeBlockValue = createMantleCodeBlockValue({
code: data.code,
language: "typescript",
preHtml: data.html,
showLineNumbers: data.showLineNumbers,
highlightLines: data.highlightLines,
lineNumberStart: data.lineNumberStart,
});
;
```
### Highlight Microservice (for Go / non-Node.js backends)
For frontends backed by non-Node.js services (Go, Python, etc.), deploy a small Node.js highlight service as a sidecar or shared microservice. This gives you the same server-rendered highlighting without shipping Shiki to the browser — the frontend just calls the API with `fetch`.
Create a highlight server using `createMantleServerSyntaxHighlighter`:
```ts
// highlight-service.ts — deploy as a sidecar alongside your Go service
import { createServer } from "node:http";
import { createMantleServerSyntaxHighlighter } from "@ngrok/mantle-server-syntax-highlighter";
const highlighter = createMantleServerSyntaxHighlighter();
createServer(async (req, res) => {
// Handle CORS
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.method === "OPTIONS") {
res.writeHead(204).end();
return;
}
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk);
const { code, language, showLineNumbers, highlightLines, lineNumberStart } = JSON.parse(
Buffer.concat(chunks).toString(),
);
const result = await highlighter.highlight({
code,
language,
showLineNumbers,
highlightLines,
lineNumberStart,
});
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(result));
}).listen(4444, () => console.log("Highlight server on :4444"));
```
Then use it from any frontend the same way as the [server-rendered example](#server-rendered-syntax-highlighting) above, pointing at the highlight service URL:
```tsx
const response = await fetch("http://localhost:4444", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, language: "json", showLineNumbers: true }),
});
const data = await response.json();
const value = createMantleCodeBlockValue({
code: data.code,
language: data.language,
preHtml: data.html,
showLineNumbers: data.showLineNumbers,
});
```
### Tabbed Code Block
Use `CodeBlock.TabList`, `CodeBlock.TabTrigger`, and `CodeBlock.TabContent` to create a tabbed code block. Pass `defaultTab` (or `activeTab` / `onActiveTabChange` for controlled mode) to `CodeBlock.Root`, place the tab triggers in the `CodeBlock.Header`, and wrap each `CodeBlock.Code` in a `CodeBlock.TabContent`. The copy button automatically copies whichever code is currently displayed.
```tsx
import { CodeBlock, mantleCode } from "@ngrok/mantle/code-block";
const policyYml = mantleCode("yaml")`…`;
const policyJson = mantleCode("json")`…`;
policy.ymlpolicy.json;
```
### `mantleCode()` Options
The `mantleCode()` tagged template accepts an optional second argument with the following options:
- **`showLineNumbers`** — Whether to show line numbers. Defaults to `true` for most languages, but `false` for single-line shell snippets (`bash`, `sh`, `shell`). Pass `false` to hide them or `true` to force them on.
- **`highlightLines`** — An array of line numbers or ranges (e.g. `[2, "4-5"]`) to visually highlight.
- **`lineNumberStart`** — The starting line number when line numbers are displayed. Defaults to `1`.
- **`indentation`** — Override the default indentation style (`"tabs"` or `"spaces"`).
```tsx
import { CodeBlock, mantleCode } from "@ngrok/mantle/code-block";
all-options.ts;
```
### Overriding Defaults
The `mantleCode()` options let you customize how code is displayed. By default, line numbers are shown starting at 1 and indentation is inferred from the language. You can also specify highlighted lines per code block. The first example below uses the defaults (YAML auto-detects space indentation), while the second overrides all options: custom indentation, line number start, highlighted lines, and visible line numbers.
```tsx
{
/* yaml auto-detects space indentation, all other defaults apply */
}
;
{
/* override all mantleCode() options */
}
{
res.writeHead(200);
res.end("Hello!");
});
`}
/>;
```
## API Reference
The `CodeBlock` renders and applies syntax highlighting to blocks of code and is composed of several sub-components.
### CodeBlock.Root
Root container for all `CodeBlock` sub-components. For tabbed code blocks, pass `defaultTab` (uncontrolled) or `activeTab` / `onActiveTabChange` (controlled) to enable tab switching.
All props from [standard HTML div attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| :------------------- | :------------------------ | :------ | :--------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `CodeBlock` styling and functionality onto alternative element types or your own React components. |
| `defaultTab?` | `string` | | The default active tab value (uncontrolled). Only relevant when using `TabList` / `TabContent`. |
| `activeTab?` | `string` | | The controlled active tab value. Only relevant when using `TabList` / `TabContent`. |
| `onActiveTabChange?` | `(value: string) => void` | | Callback fired when the active tab changes. Only relevant when using `TabList` / `TabContent`. |
### CodeBlock.Body
The body of the `CodeBlock`. This is where the `CodeBlock.Code` and optional `CodeBlock.CopyButton` are rendered as direct children.
All props from [standard HTML div attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `CodeBlock.Body` styling and functionality onto alternative element types or your own React components. |
### CodeBlock.Code
The `CodeBlock` content. This is where the code is rendered with pre-highlighted Shiki HTML.
All props from [standard HTML pre attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre#attributes), plus:
| Prop | Type | Default | Description |
| :------ | :--------------------- | :------ | :------------------------------------------------------------------------------------------------- |
| `value` | `MantleCodeBlockValue` | | The code value produced by `mantleCode("lang")` tagged template or `createMantleCodeBlockValue()`. |
### CodeBlock.Header
An optional header slot of the `CodeBlock`. This is where things like the `CodeBlock.Icon` and `CodeBlock.Title` are rendered.
All props from [standard HTML div attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `CodeBlock.Header` styling and functionality onto alternative element types or your own React components. |
### CodeBlock.Title
The (optional) title of a `CodeBlock`. Default renders as an `h3` element; use `asChild` to render something else.
All props from [standard HTML h3 attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `CodeBlock.Title` styling and functionality onto alternative element types or your own React components. |
### CodeBlock.CopyButton
The (optional) copy button of the `CodeBlock`. Render this as a child of the `CodeBlock.Body` to allow users to copy the code block contents to their clipboard.
All props from [standard HTML button attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes), plus:
| Prop | Type | Default | Description |
| :------------- | :------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onCopy?` | `(value: string) => void` | | Callback fired when the copy button is clicked, passes the copied text as an argument. |
| `onCopyError?` | `(error: unknown) => void` | | Callback fired when an error occurs during copying. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `CodeBlock.CopyButton` styling and functionality onto alternative element types or your own React components. |
### CodeBlock.ExpanderButton
The (optional) expander button of the `CodeBlock`. Render this as a child of the `CodeBlock.Root` to allow users to expand/collapse the code block contents.
All props from [standard HTML button attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `CodeBlock.ExpanderButton` styling and functionality onto alternative element types or your own React components. |
### CodeBlock.Icon
A small icon that represents the type of code block being displayed, rendered as an SVG next to the code block title in the code block header. You can pass in a custom SVG component or use one of the presets (you can exclusively pass one of `svg` or `preset`).
All props from [Icon](/components/icon), plus:
| Prop | Type | Default | Description |
| :-------- | :---------------------------------------- | :------ | :--------------------------------------------------------------------------------------------------- |
| `svg?` | `ReactNode` | | A custom icon to display in the code block header. You can exclusively pass one of `svg` or `preset` |
| `preset?` | `"cli"` \| `"file"` \| `"traffic-policy"` | | A preset icon to display in the code block header. You can exclusively pass one of `svg` or `preset` |
### CodeBlock.TabList
A tab list for the `CodeBlock` header. Renders pill-styled tab triggers that switch which code is displayed. Place this inside `CodeBlock.Header` and pair with `CodeBlock.TabContent` in `CodeBlock.Body`. Tab state is managed by `CodeBlock.Root` via `defaultTab` / `activeTab` / `onActiveTabChange`.
All props from [standard HTML div attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes).
### CodeBlock.TabTrigger
A pill-styled tab trigger for the `CodeBlock` header. Must be rendered within a `CodeBlock.TabList`.
All props from [standard HTML button attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes), plus:
| Prop | Type | Default | Description |
| :------ | :------- | :------ | :-------------------------------------------------------------------------------------------- |
| `value` | `string` | | The tab value this trigger activates. Must match the `value` of a corresponding `TabContent`. |
### CodeBlock.TabContent
Conditionally renders its children when the associated tab is active. Pair with `CodeBlock.TabList` and `CodeBlock.TabTrigger` in the header, and set `defaultTab` / `activeTab` on `CodeBlock.Root`.
All props from [standard HTML div attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| :------ | :------- | :------ | :--------------------------------------------------------------------------------------------- |
| `value` | `string` | | The tab value this content is associated with. Only rendered when this matches the active tab. |
---
---
title: Combobox
description: Fill in a React input field with autocomplete & autosuggest functionalities. Choose from a list of suggested values with full keyboard support.
---
# Combobox
Fill in a React input field with autocomplete & autosuggest functionalities. Choose from a list of suggested values with full keyboard support. This component is based on the [WAI-ARIA Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) and is powered by the [ariakit Combobox](https://ariakit.org/components/combobox).
## When to use
Use `Combobox` for a list where the user types to filter — large static lists, async/server-side data, or any single-select where search is helpful. For small finite lists without filtering, use [`Select`](/components/select). For multi-selection, use [`Multi Select`](/components/multi-select).
```tsx
import { Combobox } from "@ngrok/mantle/combobox";
import { CirclesThreePlusIcon } from "@phosphor-icons/react/CirclesThreePlus";
Choose an ngrok subdomain
Sit dolor enim eiusmod nulla nostrud officia in magna deserunt ut ex veniam cillum.
;
```
## Composition
Compose the parts of a `Combobox` together to build your own:
```text showLineNumbers=false
Combobox.Root
├── Combobox.Input
└── Combobox.Content
├── Combobox.Group
│ ├── Combobox.GroupLabel
│ └── Combobox.Item
│ └── Combobox.ItemValue
└── Combobox.Separator
```
## API Reference
The `Combobox` components are built on top of [ariakit Combobox](https://ariakit.org/components/combobox).
### Combobox.Root
Root component for a combobox. Provides a combobox store that controls the state of Combobox components.
All props from ariakit [ComboboxProvider](https://ariakit.org/reference/combobox-provider).
### Combobox.Input
Renders a combobox input element that can be used to filter a list of items.
All props from ariakit [Combobox](https://ariakit.org/reference/combobox), plus:
| Prop | Type | Default | Description |
| :-------------- | :------------------------------------------------------------------------------------------------- | :--------- | :---------------------------------------------------- |
| `autoComplete?` | `string` | `"list"` | The autocomplete behavior of the input. |
| `autoSelect?` | `"always" \| "inline" \| boolean` | `"always"` | How the first item is automatically selected. |
| `validation?` | `"error" \| "success" \| "warning" \| false \| (() => "error" \| "success" \| "warning" \| false)` | | Validation state that changes the border and outline. |
### Combobox.Content
Renders a popover that contains combobox content, e.g. items, groups, and separators.
All props from ariakit [ComboboxPopover](https://ariakit.org/reference/combobox-popover), plus:
| Prop | Type | Default | Description |
| :--------------- | :-------- | :------ | :-------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Compose onto an alternative element type or your own React component. |
| `sameWidth?` | `boolean` | `true` | Whether the popover should be the same width as the input. |
| `unmountOnHide?` | `boolean` | `true` | Whether the popover should unmount when hidden. |
### Combobox.Item
Renders a combobox item inside a `Combobox.Content` component.
All props from ariakit [ComboboxItem](https://ariakit.org/reference/combobox-item), plus:
| Prop | Type | Default | Description |
| :-------------- | :-------- | :------ | :-------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Compose onto an alternative element type or your own React component. |
| `focusOnHover?` | `boolean` | `true` | Whether the item should receive focus on hover. |
### Combobox.ItemValue
Highlights the match between the current `Combobox.Input` value and parent `Combobox.Item` value. Should only be used as a child of `Combobox.Item`.
All props from ariakit [ComboboxItemValue](https://ariakit.org/reference/combobox-item-value), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :-------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Compose onto an alternative element type or your own React component. |
### Combobox.Group
Renders a group for `Combobox.Item` elements. Optionally, a `Combobox.GroupLabel` can be rendered as a child to provide a label for the group.
All props from ariakit [ComboboxGroup](https://ariakit.org/reference/combobox-group), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :-------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Compose onto an alternative element type or your own React component. |
### Combobox.GroupLabel
Renders a label in a combobox group. Should be wrapped with `Combobox.Group` so the `aria-labelledby` is correctly set on the group element.
All props from ariakit [ComboboxGroupLabel](https://ariakit.org/reference/combobox-group-label), plus:
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :-------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Compose onto an alternative element type or your own React component. |
### Combobox.Separator
Renders a separator between `Combobox.Item`s or `Combobox.Group`s.
All props from [Separator](/components/separator).
---
---
title: Command
description: A command palette that allows users to search and execute commands.
---
# Command
A command palette that allows users to search and execute commands. Built on top of [cmdk](https://cmdk.paco.me/).
### Command Example
```tsx
import { Command } from "@ngrok/mantle/command";
import { CalendarIcon, SmileyIcon, CalculatorIcon, UserIcon } from "@phosphor-icons/react";
No results found.CalendarSearch EmojiCalculatorProfile;
```
### Command Dialog Example
```tsx
import { Button } from "@ngrok/mantle/button";
import { Command, MetaKey } from "@ngrok/mantle/command";
import {
CalculatorIcon,
CalendarIcon,
CreditCardIcon,
GearIcon,
SmileyIcon,
UserIcon,
} from "@phosphor-icons/react";
import { useEffect, useState } from "react";
function useHotkey(key: string, callback: () => void) {
useEffect(() => {
const keydown = (event: KeyboardEvent) => {
if (event.key === key && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
callback();
}
};
document.addEventListener("keydown", keydown);
return () => document.removeEventListener("keydown", keydown);
});
}
function CommandDialogDemo() {
const [open, setOpen] = useState(false);
useHotkey("j", () => setOpen(!open));
return (
<>
Press{" "}
J
or
setOpen(!open)}
>
Open Command Dialog
No results found.CalendarSearch EmojiCalculatorProfile P
Billing B
Settings S
>
);
}
```
## Composition
Compose the parts of a `Command` together to build your own:
```text showLineNumbers=false
Command.Dialog.Root
├── Command.Dialog.Trigger
└── Command.Dialog.Content
├── Command.Input
└── Command.List
├── Command.Empty
├── Command.Group
│ └── Command.Item
│ └── Command.Shortcut
└── Command.Separator
```
## API Reference
The `Command` component is built on top of [cmdk](https://cmdk.paco.me/) and provides a complete set of sub-components for building command palettes.
### Command.Root
The root component for the Command. It provides the context for all other command sub-components.
All props from cmdk's [Command Root](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#command-cmdk-root).
### Command.Dialog
A compound namespace for building a command palette dialog. Composed of `Command.Dialog.Root`, `Command.Dialog.Trigger`, and `Command.Dialog.Content`.
#### Command.Dialog.Root
The root stateful component for the CommandDialog. Manages open/closed state.
All props from Radix UI's [Dialog.Root](https://www.radix-ui.com/primitives/docs/components/dialog#root).
#### Command.Dialog.Trigger
A button that opens the CommandDialog when clicked. Supports `asChild` for custom trigger elements.
All props from Radix UI's [Dialog.Trigger](https://www.radix-ui.com/primitives/docs/components/dialog#trigger).
#### Command.Dialog.Content
The visible content of the CommandDialog. Renders inside the dialog portal and wraps the command palette UI.
| Prop | Type | Default | Description |
| :----------------- | :------------------------------------------ | :--------------------------------- | :------------------------------------------------------- |
| `title?` | `string` | `"Command Palette"` | Accessible title for the dialog (visually hidden). |
| `description?` | `string` | `"Search for a command to run..."` | Accessible description for the dialog (visually hidden). |
| `className?` | `string` | | Class name(s) to apply to the dialog content. |
| `showCloseButton?` | `boolean` | `true` | Whether to show the close button. |
| `filter?` | `(value: string, search: string) => number` | | Custom filter function for the command list. |
| `shouldFilter?` | `boolean` | | Whether to enable built-in filtering of command items. |
### Command.Input
The input component for the Command. It provides the input for the command palette.
All props from cmdk's [Command Input](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#input-cmdk-input).
### Command.List
The list component for the Command. It provides the scrollable list for the command palette.
All props from cmdk's [Command List](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#list-cmdk-list).
### Command.Empty
The empty component for the Command. Displayed when no results match the search query.
All props from cmdk's [Command Empty](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#empty-cmdk-empty).
### Command.Group
The group component for the Command. Used to group related command items together.
All props from cmdk's [Command Group](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#group-cmdk-group-hidden).
### Command.Item
The item component for the Command. Represents a selectable command in the palette.
All props from cmdk's [Command Item](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#item-cmdk-item-data-disabled-data-selected).
### Command.Separator
A visual separator between command groups or items. Automatically hidden when there is an active search query.
All props from cmdk's [Command Separator](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#separator-cmdk-separator).
### Command.Shortcut
Displays a keyboard shortcut hint within a command item. Renders as a styled `span` element.
All props from the HTML `span` element.
### MetaKey
Renders the platform-appropriate meta key label (`⌘` for macOS/iOS or `Ctrl` for other platforms). It detects the platform on mount and is SSR-safe, defaulting to `Ctrl` to avoid hydration mismatches.
Use it in keyboard shortcut hints and `Command.Shortcut` labels to ensure the correct modifier key is displayed for each platform.
```tsx
import { Command, MetaKey } from "@ngrok/mantle/command";
K
S
```
### useCommandState
A hook for accessing the command palette state.
All props from cmdk's [useCommandState](https://github.com/pacocoursey/cmdk?tab=readme-ov-file#usecommandstatestate--stateselectedfield).
---
---
title: Data Table
description: Tables purposefully designed for dynamic, application data with features like sorting, filtering, and pagination.
---
# Data Table
Tables purposefully designed for dynamic, application data with features like sorting, filtering, and pagination. Powered by [TanStack Table](https://tanstack.com/table/latest/docs/introduction).
## When to use
A `DataTable` is for **dynamic, application data** — anywhere users need to sort, filter, paginate, select, or click rows. It is built on top of [`Table`](/components/table) and wires up [TanStack Table](https://tanstack.com/table/latest/docs/introduction) so you get those behaviors out of the box.
- Prefer [`Table`](/components/table) for **static, presentational** tabular content (pricing matrices, reference tables, invoice summaries).
- All TanStack Table utilities (`createColumnHelper`, `getCoreRowModel`, `getSortedRowModel`, `getPaginationRowModel`, `getFilteredRowModel`, `useReactTable`, etc.) are re-exported from `@ngrok/mantle/data-table` — you do not need to add `@tanstack/react-table` as a separate dependency.
## Quick start
The minimum viable `DataTable`. Copy, paste, replace the type and data:
```tsx
import {
DataTable,
createColumnHelper,
getCoreRowModel,
useReactTable,
} from "@ngrok/mantle/data-table";
type Row = { id: string; name: string };
const columnHelper = createColumnHelper();
const columns = [
columnHelper.accessor("name", {
id: "name",
header: (props) => (
Name
),
cell: (props) => {props.getValue()},
}),
];
function MyTable({ data }: { data: Row[] }) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
const rows = table.getRowModel().rows;
return (
{rows.length > 0 ? (
rows.map((row) => )
) : (
No results.
)}
);
}
```
A fuller example matching the demo above — sortable columns, pagination, filtering, row-click navigation, and a sticky action column:
```tsx
import {
DataTable,
createColumnHelper,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@ngrok/mantle/data-table";
import { href, useNavigate } from "react-router";
import { useMemo } from "react";
type Payment = {
id: string;
amount: number;
status: "pending" | "processing" | "success" | "failed";
email: string;
};
const columnHelper = createColumnHelper();
const columns = [
columnHelper.accessor("id", {
id: "id",
header: (props) => (
ID
),
cell: (props) => {props.getValue()},
}),
// ... more columns
];
function PaymentsExample() {
const navigate = useNavigate();
const data = useMemo(() => examplePayments, []);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
initialState: {
sorting: [{ id: "email", desc: false }],
pagination: { pageSize: 100 },
},
});
const rows = table.getRowModel().rows;
return (
{rows.length > 0 ? (
rows.map((row) => (
{
navigate(href("/payments/:id", { id: row.original.id }));
}}
row={row}
/>
))
) : (
No results.
)}
);
}
```
## Composition
Compose the parts of a `DataTable` together to build your own:
```text showLineNumbers=false
DataTable.Root
├── DataTable.Head
│ └── DataTable.Row
│ ├── DataTable.Header
│ │ └── DataTable.HeaderSortButton
│ └── DataTable.ActionHeader
└── DataTable.Body
├── DataTable.Row
│ ├── DataTable.Cell
│ └── DataTable.ActionCell
└── DataTable.EmptyRow
```
## Rules
Follow these invariants for a correctly styled, accessible, and behaving `DataTable`.
1. **Type columns with `createColumnHelper()`.** It threads `TData` through `header`, `cell`, and `row.original` so consumers get inference instead of `unknown`.
2. **Wrap every body cell in `DataTable.Cell`.** A raw `
` skips the mantle typography, padding, and sticky-column styling.
3. **Wrap every header in `DataTable.Header`.** For sortable columns, also wrap its contents in `DataTable.HeaderSortButton` — the icon, cycling behavior, and screen-reader announcements are provided by that button.
4. **Key rows with `row.id`.** TanStack Table tracks row identity across sort/filter/pagination; using array indexes will re-mount rows incorrectly.
5. **Always branch on `rows.length > 0` and render `DataTable.EmptyRow`** for the empty case. The empty row spans all columns and preserves the table's frame — returning `null` leaves an empty `
` and collapses the frame.
6. **Place an action column last, using `columnHelper.display({ ... })`.** Pair `DataTable.ActionHeader` (in `header`) with `DataTable.ActionCell` (in `cell`) so the pinned column aligns across header and body when scrolling horizontally.
7. **Pass `onClick` to `DataTable.Row` for row-click behavior.** The row auto-applies `cursor-pointer` when `onClick` is set — do not add it yourself. Override with another `cursor-*` class (for example, `cursor-wait`) via `className` if needed.
8. **Stop click propagation inside `DataTable.ActionCell` when the row is clickable.** Without it, clicks on dropdown triggers, buttons, and links inside the action cell will bubble and fire the row `onClick`.
9. **Provide a keyboard-accessible equivalent for row navigation.** A `
` is not focusable and is not announced as interactive to assistive tech. If clicking a row navigates, render a `` in the primary cell so keyboard and screen-reader users have a reachable equivalent.
10. **Register the row models you use.** `useReactTable` only wires up what you pass in: `getSortedRowModel()` for sorting, `getPaginationRowModel()` for pagination, `getFilteredRowModel()` for filtering. Missing one and the corresponding feature silently no-ops.
## Anti-patterns
Common mistakes. The left column is what not to do; the right column is the fix.
```tsx
// ❌ Raw
— misses mantle styling
cell: (props) =>
{props.getValue()}
,
// ✅
cell: (props) => {props.getValue()},
// ❌ Manual cursor-pointer — redundant, can desync from behavior
// ✅
// ❌ Plain button for a sortable header — no icon, no ARIA
header: () => column.toggleSorting()}>Name,
// ✅
header: (props) => (
Name
),
// ❌ Clickable row with a dropdown inside — trigger click fires the row onClick
...
// ✅ Stop propagation at the action cell boundary
event.stopPropagation()}>
...
// ❌ Empty state returns null — collapses the table frame
{rows.map((row) => )}
// ✅ Use DataTable.EmptyRow
{rows.length > 0
? rows.map((row) => )
: No results.}
// ❌ Declared sorting but forgot the row model
useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
// ✅
useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
```
## Recipes
### Navigating on row click
Pass `onClick` to `DataTable.Row` and navigate with React Router's `href()` + `useNavigate()`. Render a `` inside the primary cell for keyboard and screen-reader users — the row `onClick` acts as a larger pointer target on top.
```tsx
import { DataTable } from "@ngrok/mantle/data-table";
import { Link, href, useNavigate } from "react-router";
const columns = [
columnHelper.accessor("id", {
id: "id",
header: (props) => (
ID
),
cell: (props) => (
{props.getValue()}
),
}),
// ... more columns
];
function PaymentsTable({ data }: { data: Payment[] }) {
const navigate = useNavigate();
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
const rows = table.getRowModel().rows;
return (
{rows.map((row) => (
{
navigate(href("/payments/:id", { id: row.original.id }));
}}
row={row}
/>
))}
);
}
```
### Action column with a dropdown menu
Define the action column with `columnHelper.display`, pair `DataTable.ActionHeader` with `DataTable.ActionCell`, and stop click propagation on the cell if the row is also clickable.
```tsx
import { IconButton } from "@ngrok/mantle/button";
import { DataTable } from "@ngrok/mantle/data-table";
import { DropdownMenu } from "@ngrok/mantle/dropdown-menu";
import { Icon } from "@ngrok/mantle/icon";
import { DotsThreeIcon } from "@phosphor-icons/react/DotsThree";
import { PencilSimpleIcon } from "@phosphor-icons/react/PencilSimple";
import { TrashIcon } from "@phosphor-icons/react/Trash";
columnHelper.display({
id: "actions",
header: () => ,
cell: (props) => (
event.stopPropagation()}>
}
/>
editRow(props.row.original)}>
} /> Edit
deleteRow(props.row.original)}
>
} /> Delete
),
});
```
### Customizing cells (badges, numeric, truncation)
Cell rendering is just React. Use `Badge` for status pills, `text-right` for numeric columns, and `truncate max-w-*` for long strings.
```tsx
import { Badge } from "@ngrok/mantle/badge";
import { DataTable } from "@ngrok/mantle/data-table";
// Status pill
columnHelper.accessor("status", {
id: "status",
header: (props) => (
Status
),
cell: (props) => {
const status = props.getValue();
const color = status === "success" ? "green" : status === "failed" ? "red" : "amber";
return (
{status}
);
},
}),
// Right-aligned numeric — the header button also needs justify-end + iconPlacement="start"
// so the sort affordance stays visually paired with the label
columnHelper.accessor("amount", {
id: "amount",
header: (props) => (
Amount
),
cell: (props) => (
${props.getValue().toFixed(2)}
),
}),
// Truncate a long URL
columnHelper.accessor("url", {
id: "url",
header: (props) => (
URL
),
cell: (props) => {props.getValue()},
}),
```
### Pagination controls
Register `getPaginationRowModel()` and drive page controls from the table instance (`table.getState().pagination`, `table.previousPage()`, `table.nextPage()`, `table.setPageIndex()`, `table.getPageCount()`).
```tsx
import { Button } from "@ngrok/mantle/button";
import {
DataTable,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@ngrok/mantle/data-table";
function PaginatedTable({ data }: { data: Payment[] }) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: { pagination: { pageSize: 25 } },
});
const rows = table.getRowModel().rows;
const { pageIndex } = table.getState().pagination;
return (
<>
{rows.length > 0 ? (
rows.map((row) => )
) : (
No results.
)}
Page {pageIndex + 1} of {table.getPageCount()}
table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
table.nextPage()} disabled={!table.getCanNextPage()}>
Next
>
);
}
```
### Row selection with checkboxes
Use `columnHelper.display` for the checkbox column and read selection from `table.getState().rowSelection`.
```tsx
import { Checkbox } from "@ngrok/mantle/checkbox";
import { DataTable, getCoreRowModel, useReactTable } from "@ngrok/mantle/data-table";
import { useState } from "react";
const columns = [
columnHelper.display({
id: "select",
header: ({ table }) => (
table.toggleAllRowsSelected(value === true)}
aria-label="Select all rows"
/>
),
cell: ({ row }) => (
row.toggleSelected(value === true)}
aria-label="Select row"
/>
),
}),
// ... data columns
];
function SelectableTable({ data }: { data: Payment[] }) {
const [rowSelection, setRowSelection] = useState({});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
state: { rowSelection },
onRowSelectionChange: setRowSelection,
enableRowSelection: true,
});
const selectedRows = table.getSelectedRowModel().rows;
return (
{table.getRowModel().rows.map((row) => (
))}
);
}
```
## API Reference
The `DataTable` components are built on top of [TanStack Table](https://tanstack.com/table/latest/docs/introduction). All TanStack Table utilities (`createColumnHelper`, `getCoreRowModel`, `getSortedRowModel`, `getPaginationRowModel`, `getFilteredRowModel`, `useReactTable`, etc.) are re-exported from `@ngrok/mantle/data-table`.
### DataTable.Root
The root container for the data table. Wraps all other `DataTable` sub-components and provides the table context. Delegates rendering to [`Table.Root`](/components/table#tableroot).
| Prop | Type | Default | Description |
| :----------- | :--------------------- | :------ | :----------------------------------------------------------------------------------- |
| `table` | `TableInstance` | | The TanStack Table instance returned from `useReactTable`. **Required.** |
| `children` | `ReactNode` | | Typically `DataTable.Head` and `DataTable.Body`. |
| `className?` | `string` | | Extra classes merged onto the wrapper. Remaining props forward to the wrapper `div`. |
### DataTable.Head
Automatically renders column headers from the table instance by iterating `table.getHeaderGroups()`. Does not accept children — the headers come from each column's `header` definition.
| Prop | Type | Default | Description |
| :----------- | :------- | :------ | :---------------------------------------------------------------------------------------------- |
| `className?` | `string` | | Extra classes merged onto the ``. Remaining props forward to the HTML `` element. |
### DataTable.Body
The `` container for rows of data. Typically wraps a map of `DataTable.Row` or a fallback `DataTable.EmptyRow`.
| Prop | Type | Default | Description |
| :----------- | :---------- | :------ | :---------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | `DataTable.Row` elements, or a `DataTable.EmptyRow` when there is no data. |
| `className?` | `string` | | Extra classes merged onto the ``. Remaining props forward to the HTML `` element. |
### DataTable.Row
Renders a single body row using the column definitions from the table instance. Does not accept children — cells come from each column's `cell` definition.
When `onClick` is provided, the row automatically receives `cursor-pointer`. Pass a different `cursor-*` class via `className` (for example, `cursor-wait`) to override.
| Prop | Type | Default | Description |
| :----------- | :------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------- |
| `row` | `TableRow` | | The TanStack Table row instance to render. **Required.** |
| `onClick?` | `(event: MouseEvent) => void` | | Fires when the row is clicked. Applying this auto-adds `cursor-pointer`. |
| `className?` | `string` | | Extra classes merged onto the `
`. A `cursor-*` class here overrides the auto `cursor-pointer`. |
Remaining props forward to the HTML `
` element.
### DataTable.EmptyRow
An empty-state row that spans every column. Render this as the `else` branch when `rows.length === 0` to keep the table's frame intact.
| Prop | Type | Default | Description |
| :----------- | :---------- | :------ | :---------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | The empty-state content (e.g. a centered "No results" message). |
| `className?` | `string` | | Extra classes merged onto the `
`. Remaining props forward to the HTML `
` element. |
### DataTable.Header
A `
` cell optimized for header actions. Wrap sortable headers' contents in `DataTable.HeaderSortButton`.
| Prop | Type | Default | Description |
| :----------- | :---------- | :------ | :---------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | Usually a `DataTable.HeaderSortButton`. For non-sortable columns, plain text is fine. |
| `className?` | `string` | | Extra classes merged onto the `
`. Remaining props forward to the HTML `
` element. |
### DataTable.HeaderSortButton
A sortable button toggle for column headers. Clicking cycles through sort directions: `unsorted → asc → desc → unsorted` for `"alphanumeric"`, and `unsorted → desc → asc → unsorted` (newest-first) for `"time"`. Renders a sort icon that reflects the current direction.
| Prop | Type | Default | Description |
| :---------------- | :----------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `column` | `Column` | | The TanStack Table column instance (from `props.column` in `header`). **Required.** |
| `sortingMode` | `"alphanumeric" \| "time"` | | Which direction sequence to cycle through. **Required** unless `disableSorting` is `true`. |
| `disableSorting?` | `boolean` | `false` | Prevents sorting and hides the sort icon. Makes `sortingMode` unnecessary. |
| `sortIcon?` | `(sortDirection: SortDirection) => ReactNode` | | Override the default sort icon. `sortDirection` is `"asc" \| "desc" \| "unsorted"`. |
| `iconPlacement?` | `"start" \| "end"` | `"end"` | The side the sort icon renders on. Use `"start"` for right-aligned numeric columns. |
| `onClick?` | `(event: MouseEvent) => void` | | Called before the sort toggles. Call `event.preventDefault()` to skip the toggle. |
| `className?` | `string` | | Extra classes merged onto the underlying [`Button`](/components/button) — for example, `justify-end` for right-aligned numeric columns. |
All additional props from [`Button`](/components/button).
### DataTable.Cell
A `
` for rendering individual data cells. Provides mantle typography, padding, and alignment. Re-exported from [`Table.Cell`](/components/table#tablecell).
| Prop | Type | Default | Description |
| :----------- | :---------- | :------ | :---------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | The cell's rendered value. Any React node is allowed. |
| `className?` | `string` | | Extra classes merged onto the `
`. Remaining props forward to the HTML `
` element. |
### DataTable.ActionHeader
A sticky-right `
` that pairs with `DataTable.ActionCell`. Use as the `header` for your action column so the pinned column stays aligned across the header and every body row when the table scrolls horizontally. Automatically opts out of stickiness when the table is empty so the scroll-fade shows correctly.
| Prop | Type | Default | Description |
| :----------- | :---------- | :------ | :---------------------------------------------------------------------------------------- |
| `children?` | `ReactNode` | | Header content. Usually empty for action columns. |
| `className?` | `string` | | Extra classes merged onto the `
`. Remaining props forward to the HTML `
` element. |
### DataTable.ActionCell
A sticky-right `
` for per-row action buttons (typically an `IconButton` that opens a `DropdownMenu`).
When the row has an `onClick`, pass `onClick={(event) => event.stopPropagation()}` on the action cell so clicks on controls inside do not bubble and trigger the row handler.
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------------------- | :------ | :---------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | The row's action controls. |
| `onClick?` | `(event: MouseEvent) => void` | | Use with `event.stopPropagation()` to prevent bubbling to a clickable row. |
| `className?` | `string` | | Extra classes merged onto the `
`. Remaining props forward to the HTML `
` element. |
---
---
title: Description List
description: A semantically correct description list built on the HTML dl element. Renders a list of label/value pairs, commonly used in detail views to display metadata about a resource.
---
# Description List
A semantically correct description list built on the HTML [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl) element. Renders a list of label/value pairs, commonly used in detail views to display metadata about a resource.
Compose `` with ``, ``, and `` as direct children.
```tsx
import { DescriptionList } from "@ngrok/mantle/description-list";
Keymy-api-keyIDaigk_2fKm9x8Hn3QpYT7zKlR0vW5DescriptionProduction API key for the billing serviceCreated2 days ago by admin@example.comLast UsedNeverMetadata17 Bytes;
```
## Composition
Compose the parts of a `DescriptionList` together to build your own:
```text showLineNumbers=false
DescriptionList.Root
└── DescriptionList.Item
├── DescriptionList.Label
└── DescriptionList.Value
```
## API Reference
### DescriptionList.Root
The root container for a description list. Renders a `
` element.
All props from [dl](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl#attributes), plus:
| Prop | Type | Default | Description |
| ------------ | ----------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `children?` | `ReactNode` | | Compose `DescriptionList.Item` components as direct children. |
| `className?` | `string` | | Additional CSS class names to apply to the root element. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `DescriptionList.Root` styling onto alternative element types or your own React components. |
### DescriptionList.Item
A wrapper that groups a `DescriptionList.Label` and `DescriptionList.Value` pair. Renders a `
` with a default subgrid layout that inherits column tracks from the root.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ------------ | ----------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `children?` | `ReactNode` | | A `DescriptionList.Label` and `DescriptionList.Value` pair. |
| `className?` | `string` | | Additional CSS class names to apply to the item wrapper. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `DescriptionList.Item` styling onto alternative element types or your own React components. |
### DescriptionList.Label
The label for a description list item. Renders a `
` element.
All props from [dt](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt#attributes), plus:
| Prop | Type | Default | Description |
| ------------ | ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `children?` | `ReactNode` | | The label text for this description list item. |
| `className?` | `string` | | Additional CSS class names to apply to the label element. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `DescriptionList.Label` styling onto alternative element types or your own React components. |
### DescriptionList.Value
The value for a description list item. Renders a `
` element. Compose any content inside — the component imposes no layout on its children.
All props from [dd](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd#attributes), plus:
| Prop | Type | Default | Description |
| ------------ | ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `children?` | `ReactNode` | | The value content for this description list item. Can be any React node — text, formatted values, interactive elements, etc. |
| `className?` | `string` | | Additional CSS class names to apply to the value element. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the `DescriptionList.Value` styling onto alternative element types or your own React components. |
---
---
title: Dialog
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
---
# Dialog
A window overlaid on either the primary window or another dialog window, rendering the content underneath inert. Built on top of Radix UI Dialog.
## When to use
Use `Dialog` for a centered modal that interrupts the user to gather input or confirm a non-destructive choice. For destructive confirmations (delete, irreversible actions), use [`AlertDialog`](/components/alert-dialog). For side-panel content (filter panels, detail/inspector views, navigation drawers), use [`Sheet`](/components/sheet).
```tsx
import { Dialog } from "@ngrok/mantle/dialog";
import { Button } from "@ngrok/mantle/button";
Open dialog
Are you absolutely sure?
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
Delete
Cancel
;
```
## Handling Autofill Overlays
When a dialog contains form inputs, browser autofill or password-manager plugins (e.g. 1Password) may render overlays as siblings to the dialog content. By default, clicking these overlays triggers `onPointerDownOutside`, which closes the dialog. Use `isDialogOverlayTarget` to prevent this:
```tsx
import { Dialog, isDialogOverlayTarget } from "@ngrok/mantle/dialog";
{
// Allow closing only when clicking the actual overlay backdrop,
// not browser autofill/password-manager popups
if (isDialogOverlayTarget(event.target)) {
return;
}
event.preventDefault();
}}
>
{/* dialog content with form inputs */}
;
```
## Composition
Compose the parts of a `Dialog` together to build your own:
```text showLineNumbers=false
Dialog.Root
├── Dialog.Trigger
└── Dialog.Content
├── Dialog.Header
│ ├── Dialog.Title
│ ├── Dialog.Description
│ └── Dialog.CloseIconButton
├── Dialog.Body
└── Dialog.Footer
└── Dialog.Close
```
## Combining with a Tooltip
In some cases, you might wish to have a tooltip over the dialog trigger. This is helpful if the dialog trigger is an `IconButton` and you wish to provide more context to what the button does. You can compose them both together to where the dialog trigger is also the tooltip trigger.
```tsx
import { Dialog } from "@ngrok/mantle/dialog";
import { Button, IconButton } from "@ngrok/mantle/button";
import { Tooltip } from "@ngrok/mantle/tooltip";
import { TrashSimpleIcon } from "@phosphor-icons/react/TrashSimple";
} />
Delete
Are you absolutely sure?
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
Delete
Cancel
;
```
## API Reference
### Dialog.Root
The root stateful component that manages the open/closed state of the dialog.
| Prop | Type | Default | Description |
| --------------- | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `open?` | `boolean` | | The controlled open state of the dialog. Must be used in conjunction with `onOpenChange`. |
| `onOpenChange?` | `(open: boolean) => void` | | Event handler called when the open state of the dialog changes. |
| `defaultOpen?` | `boolean` | `false` | The open state of the dialog when it is initially rendered. Use when you do not need to control its open state. |
| `modal?` | `boolean` | `true` | The modality of the dialog. When set to `true`, interaction with outside elements will be disabled and only dialog content will be visible to screen readers. |
### Dialog.Trigger
A button that opens the dialog.
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------------------------------------------ |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the trigger functionality onto your own component. |
### Dialog.Content
The container for the dialog content. Renders on top of the overlay and is centered in the viewport.
| Prop | Type | Default | Description |
| -------------------- | -------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `preferredWidth?` | `` `max-w-${string}` `` | `"max-w-lg"` | The preferred width of the dialog content as a Tailwind `max-w-` class. Controls the maximum width of the dialog. |
| `onEscapeKeyDown?` | `(event: KeyboardEvent) => void` | | Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| `onInteractOutside?` | `(event: Event) => void` | | Event handler called when the user interacts outside the component. It can be prevented by calling `event.preventDefault`. |
### Dialog.Header
Contains the header content of the dialog, including the title and close button.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ----------------------------------------------- |
| `children` | `ReactNode` | | The content to render inside the dialog header. |
### Dialog.Body
Contains the main content of the dialog.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | --------------------------------------------- |
| `children` | `ReactNode` | | The content to render inside the dialog body. |
### Dialog.Footer
Contains the footer content of the dialog, including action buttons.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | ---------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | The content to render inside the dialog footer. Typically contains action buttons. |
### Dialog.Title
An accessible name to be announced when the dialog is opened.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | --------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | The title text for the dialog. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to render a custom element instead of the default heading. |
### Dialog.Description
An accessible description to be announced when the dialog is opened.
| Prop | Type | Default | Description |
| ---------- | ----------- | ------- | -------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | | The description text for the dialog. Enhances accessibility by providing additional context. |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to render a custom element instead of the default paragraph. |
### Dialog.Close
A button that closes the dialog when clicked.
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ---------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the close functionality onto your own component. |
### Dialog.CloseIconButton
An icon button that closes the dialog when clicked.
| Prop | Type | Default | Description |
| ------------- | -------------------------- | ---------------- | ------------------------------------------------------------------------ |
| `size?` | `"sm"` \| `"md"` \| `"lg"` | `"md"` | The size of the close icon button. |
| `label?` | `string` | `"Close Dialog"` | The accessible label for the close button. Important for screen readers. |
| `appearance?` | `"ghost"` \| `"outlined"` | `"ghost"` | The visual appearance of the close icon button. |
---
---
title: Dropdown Menu
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
---
# Dropdown Menu
Displays a menu to the user — such as a set of actions or functions — triggered by a button. Built on top of [Radix Dropdown Menu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu).
```tsx
import { Button } from "@ngrok/mantle/button";
import { DropdownMenu } from "@ngrok/mantle/dropdown-menu";
import { Icon } from "@ngrok/mantle/icon";
Open Menu
corby.pickles@ngork.com} />
System Preference
} />
User Settings
} />
Log out
;
```
## Composition
Compose the parts of a `DropdownMenu` together to build your own:
```text showLineNumbers=false
DropdownMenu.Root
├── DropdownMenu.Trigger
└── DropdownMenu.Content
├── DropdownMenu.Group
│ ├── DropdownMenu.Label
│ ├── DropdownMenu.Item
│ │ └── DropdownMenu.Shortcut
│ ├── DropdownMenu.CheckboxItem
│ └── DropdownMenu.RadioGroup
│ └── DropdownMenu.RadioItem
├── DropdownMenu.Separator
└── DropdownMenu.Sub
├── DropdownMenu.SubTrigger
└── DropdownMenu.SubContent
```
## API Reference
The `DropdownMenu` components are built on top of [Radix Dropdown Menu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu).
### DropdownMenu.Root
The root, stateful component that manages the open/closed state of the dropdown menu.
| Prop | Type | Default | Description |
| :-------------- | :------------------------ | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `open?` | `boolean` | | The controlled open state of the dropdown menu. Must be used in conjunction with `onOpenChange`. |
| `onOpenChange?` | `(open: boolean) => void` | | Event handler called when the open state of the dropdown menu changes. |
| `defaultOpen?` | `boolean` | `false` | The open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state. |
| `dir?` | `"ltr"` \| `"rtl"` | | The reading direction of submenus when applicable. |
| `modal?` | `boolean` | `true` | The modality of the dropdown menu. When set to `true`, interaction with outside elements will be disabled and only menu content will be visible to screen readers. |
### DropdownMenu.Trigger
The trigger button that opens the dropdown menu.
| Prop | Type | Default | Description |
| :--------- | :-------- | :------ | :----------------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Use the `asChild` prop to compose the trigger functionality onto your own component. |
### DropdownMenu.Content
The container for the dropdown menu content. Appears in a portal with scrolling and animations.
All props from Radix [DropdownMenu.Content](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#content), plus:
| Prop | Type | Default | Description |
| :------------- | :--------------------------------------------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `width?` | `"trigger"` \| `"content"` | | `trigger` will ensure the dropdown content is the same width as the trigger button. `content` will make the dropdown content use the intrinsic content width. |
| `side?` | `"top"` \| `"right"` \| `"bottom"` \| `"left"` | `"bottom"` | The preferred side of the trigger to render against when open. |
| `sideOffset?` | `number` | | The distance in pixels from the trigger. |
| `align?` | `"start"` \| `"center"` \| `"end"` | `"center"` | The preferred alignment against the trigger. |
| `alignOffset?` | `number` | `0` | An offset in pixels from the `start` or `end` alignment options. |
| `loop?` | `boolean` | `true` | When `true`, keyboard navigation will loop from last item to first, and vice versa. |
### DropdownMenu.Item
A standard item in the dropdown menu that can be selected or activated.
| Prop | Type | Default | Description |
| :---------- | :----------- | :------ | :--------------------------------------------------------------------- |
| `inset?` | `boolean` | | When `true`, adds left padding to align with items that have icons. |
| `disabled?` | `boolean` | | When `true`, prevents the user from interacting with the item. |
| `onSelect?` | `() => void` | | Event handler called when the user selects an item (via mouse or key). |
### DropdownMenu.CheckboxItem
A menu item with a checkbox that can be controlled or uncontrolled.
All props from Radix [DropdownMenu.CheckboxItem](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#checkboxitem), including:
| Prop | Type | Default | Description |
| :----------------- | :----------------------------- | :------ | :----------------------------------------------------------------------------- |
| `checked?` | `boolean` \| `"indeterminate"` | | The controlled checked state of the item. Must be used with `onCheckedChange`. |
| `onCheckedChange?` | `(checked: boolean) => void` | | Event handler called when the checked state changes. |
| `disabled?` | `boolean` | | When `true`, prevents the user from interacting with the item. |
### DropdownMenu.RadioGroup
A radio group container for exclusive selection within the dropdown menu.
| Prop | Type | Default | Description |
| :--------------- | :------------------------ | :------ | :------------------------------------------- |
| `value?` | `string` | | The value of the selected item in the group. |
| `onValueChange?` | `(value: string) => void` | | Event handler called when the value changes. |
### DropdownMenu.RadioItem
A radio item where only one item in the group can be selected at a time.
All props from Radix [DropdownMenu.RadioItem](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#radioitem), including:
| Prop | Type | Default | Description |
| :---------- | :-------- | :------ | :------------------------------------------------------------- |
| `value` | `string` | | The unique value of the item. |
| `disabled?` | `boolean` | | When `true`, prevents the user from interacting with the item. |
### DropdownMenu.Label
A label for grouping and describing sections within the dropdown menu.
| Prop | Type | Default | Description |
| :------- | :-------- | :------ | :------------------------------------------------------------------ |
| `inset?` | `boolean` | | When `true`, adds left padding to align with items that have icons. |
### DropdownMenu.Group
A group container for organizing related dropdown menu items. Accepts all props from Radix [DropdownMenu.Group](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#group).
### DropdownMenu.Separator
A visual separator for dividing sections within the dropdown menu. All props from [`Separator`](/components/separator).
### DropdownMenu.Shortcut
A keyboard shortcut indicator for dropdown menu items. All props from [span](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span#attributes).
### DropdownMenu.Sub
A submenu container for creating nested dropdown menus. Accepts all props from Radix [DropdownMenu.Sub](https://www.radix-ui.com/primitives/docs/components/dropdown-menu#sub).
| Prop | Type | Default | Description |
| :-------------- | :------------------------ | :------ | :----------------------------------------------------------------------------------------- |
| `open?` | `boolean` | | The controlled open state of the submenu. Must be used in conjunction with `onOpenChange`. |
| `onOpenChange?` | `(open: boolean) => void` | | Event handler called when the open state of the submenu changes. |
| `defaultOpen?` | `boolean` | | The open state of the submenu when it is initially rendered. |
### DropdownMenu.SubTrigger
The trigger item that opens a submenu when hovered or focused.
| Prop | Type | Default | Description |
| :------- | :-------- | :------ | :------------------------------------------------------------------ |
| `inset?` | `boolean` | | When `true`, adds left padding to align with items that have icons. |
### DropdownMenu.SubContent
The content container for submenu items. Appears in a portal with scrolling and animations.
| Prop | Type | Default | Description |
| :------ | :-------- | :------ | :---------------------------------------------------------------------------------- |
| `loop?` | `boolean` | `true` | When `true`, keyboard navigation will loop from last item to first, and vice versa. |
---
---
title: Empty
description: Display a placeholder when there is no data or content to show.
---
# Empty
Display a placeholder when there is no data or content to show. Commonly used
inside tables, lists, or pages that have no results.
```tsx
import { Empty } from "@ngrok/mantle/empty";
import { Button } from "@ngrok/mantle/button";
import { GhostIcon, PlusIcon } from "@phosphor-icons/react";
} />
No endpoints yet
Create your first endpoint to get started.
Create endpoint
;
```
## Error page
A full-page error state with a larger heading and multiple description paragraphs.
```tsx
import { Empty } from "@ngrok/mantle/empty";
import { Button } from "@ngrok/mantle/button";
import { SmileyMeltingIcon } from "@phosphor-icons/react";
} />
Oops, something went wrong.
Please try again in a few minutes.
Retry
;
```
## No filter results
A common pattern for empty states when a filter or search yields no results.
```tsx
import { Empty } from "@ngrok/mantle/empty";
import { Button } from "@ngrok/mantle/button";
import { MagnifyingGlassIcon } from "@phosphor-icons/react";
} />
No results matched your filter.
Check your spelling. It's possible what you're looking for no longer exists.
Clear filters
;
```
## Minimal
You can use only the sub-components you need.
```tsx
import { Empty } from "@ngrok/mantle/empty";
import { GhostIcon } from "@phosphor-icons/react";
} />
Nothing here;
```
## Composition
Compose the parts of an `Empty` state together to build your own:
```text showLineNumbers=false
Empty.Root
├── Empty.Icon
├── Empty.Title
├── Empty.Description
└── Empty.Actions
```
## API Reference
### Empty.Root
The root container for an empty state. Centers content horizontally with consistent vertical padding and max-width.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Render as a different element by passing a child. |
### Empty.Icon
Renders a large icon for the empty state. Pass a single SVG icon element via the `svg` prop. The icon is automatically sized to `size-16`.
All props from [svg](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg#attributes), plus:
| Prop | Type | Default | Description |
| ----- | ----------- | ------- | -------------------------- |
| `svg` | `ReactNode` | — | A single SVG icon element. |
### Empty.Title
The heading text for the empty state. Renders as an `h3` by default. Use `asChild` to render as a different heading level.
All props from [h3](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | --------------------------------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Render as a different heading element (e.g. `h2`, `h3`) by passing a child. |
### Empty.Description
Supporting descriptive text rendered below the title. Renders as a `div` with vertical spacing so multiple paragraphs can be placed inside.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Render as a different element by passing a child. |
### Empty.Actions
A flex container for action buttons or links.
All props from [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes), plus:
| Prop | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------- |
| `asChild?` | `boolean` | `false` | Render as a different element by passing a child. |
---
---
title: Flag
description: Displays a flag as an svg based on the provided country code.
---
# Flag
Renders a country flag from an [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code, served as an SVG from ngrok's CDN.
## When to use
- Showing the country associated with a region, IP, billing address, or locale.
- Inside a select option, list row, or status pill that needs a quick visual cue.
## When not to use
- As a stand-in for language. Flags are not languages — Brazilian Portuguese is not "Portugal", Spanish is not just "Spain". Use a language label instead.
- As decoration where the country isn't meaningful to the user.
## Sizing
`"s"` (16×12), `"m"` (20×15), and `"l"` (32×24, default) match common inline, list, and table contexts. Pick the size that matches its neighbors so the flag doesn't dominate or disappear.
## Accessibility
The underlying `` is given `alt="flag for {code}"`. If the country is decorative or already labeled in adjacent text, consider passing `aria-hidden` via the wrapper `
` to avoid duplicate announcements.
## Loading
Defaults to `loading="lazy"`. Use `loading="eager"` for flags above the fold or in critical content.
```tsx
import { Flag } from "@ngrok/mantle/flag";
```
## API Reference
The `Flag` accepts the following props in addition to the [standard HTML div attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes).
| Prop | Type | Default | Description |
| --------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `code` | `string` | | The country code of the flag to render. |
| `size` | `"s"` \| `"m"` \| `"l"` | `"l"` | The size of the flag to render. The default size is large. |
| `loading` | `"eager"` \| `"lazy"` | `"lazy"` | A string providing a hint to the user agent as to how to best schedule the loading of the image to optimize page performance. [See MDN docs.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/loading) |
---
---
title: Hover Card
description: For sighted users to preview content available behind a link.
---
# Hover Card
For sighted users to preview content available behind a link.
## When to use
A `HoverCard` shows a preview of richer content (a user card, link preview, etc.) when a sighted user hovers a link or trigger.
- Prefer [`Tooltip`](/components/tooltip) for short, non-interactive labels on controls.
- Prefer [`Popover`](/components/popover) when the content must be reachable by all users and may contain interactive elements.
> \[!WARNING]
> Do not rely on a `HoverCard` as the only accessible path to important content. Because it opens on pointer hover, it is not a reliable path for keyboard or screen reader users. Any information inside a `HoverCard` must also be available through another fully accessible path (usually the underlying link).
```tsx
import { Button } from "@ngrok/mantle/button";
import { HoverCard } from "@ngrok/mantle/hover-card";
import { Icon } from "@ngrok/mantle/icon";
import { CalendarIcon } from "@phosphor-icons/react/Calendar";
import { ShrimpIcon } from "@phosphor-icons/react/Shrimp";
Open Hover Card
} className="size-12" />
@ngrok/mantle
The Design System – created and maintained by @ngrok.
} className="mr-2 h-4 w-4 opacity-70" />{" "}
Joined November 2023
;
```
## Composition
Compose the parts of a `HoverCard` together to build your own:
```text showLineNumbers=false
HoverCard.Root
├── HoverCard.Trigger
└── HoverCard.Content
```
## API Reference
The `HoverCard` component is built on top of [Radix UI Hover Card](https://www.radix-ui.com/primitives/docs/components/hover-card).
### HoverCard.Root
The root stateful component that manages the open/closed state of the hover card.
| Prop | Type | Default | Description |
| -------------- | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `open` | `boolean` | | The controlled open state of the hover card. Must be used in conjunction with `onOpenChange`. |
| `onOpenChange` | `(open: boolean) => void` | | Event handler called when the open state of the hover card changes. |
| `defaultOpen` | `boolean` | `false` | The open state of the hover card when it is initially rendered. Use when you do not need to control its open state. |
| `openDelay` | `number` | `100` | The duration in milliseconds from when the mouse enters the trigger until the hover card opens. |
| `closeDelay` | `number` | `300` | The duration in milliseconds from when the mouse leaves the trigger or content until the hover card closes. |
### HoverCard.Trigger
The trigger element that opens the hover card when hovered.
| Prop | Type | Default | Description |
| --------- | --------- | ------- | ------------------------------------------------------------------------------------ |
| `asChild` | `boolean` | `false` | Use the `asChild` prop to compose the trigger functionality onto your own component. |
### HoverCard.Content
The content to render inside the hover card. Appears in a portal with rich styling and animations.
| Prop | Type | Default | Description |
| ------------------- | ---------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `side` | `"top"` \| `"right"` \| `"bottom"` \| `"left"` | `"bottom"` | The preferred side of the trigger to render against when open. Will be reversed when collisions occur and `avoidCollisions` is enabled. |
| `align` | `"start"` \| `"center"` \| `"end"` | `"center"` | The preferred alignment against the trigger. May change when collisions occur. |
| `sideOffset` | `number` | `4` | The distance in pixels from the trigger. |
| `alignOffset` | `number` | `0` | An offset in pixels from the `start` or `end` alignment options. |
| `avoidCollisions` | `boolean` | `true` | When `true`, overrides the `side` and `align` preferences to prevent collisions with boundary edges. |
| `collisionBoundary` | `Element \| null \| Array` | `[]` | The element used as the collision boundary. By default this is the viewport. |
| `collisionPadding` | `number \| Partial>` | `0` | The distance in pixels from the boundary edges where collision detection should occur. |
| `sticky` | `"partial"` \| `"always"` | `"partial"` | The sticky behavior on the align axis. |
| `hideWhenDetached` | `boolean` | `false` | Whether to hide the content when the trigger becomes fully occluded. |
---
---
title: Icon
description: Decorates an svg icon with automatic sizing. Useful when applying base styles to phosphor icons.
---
# Icon
Decorates an svg icon with automatic sizing. Useful when applying base styles to [phosphor icons](https://phosphoricons.com).
```tsx
import { Icon } from "@ngrok/mantle/icon";
import { FireIcon } from "@phosphor-icons/react/Fire";
} />
} />
```
## Examples
### Merging `className`s
The `Icon` merges `className` selectors with the following order of precedence (last one wins):
1. SvgOnly base classes (only `"shrink-0"`)
2. Icon base classes
3. Icon className
4. svg className
```tsx
import { Icon } from "@ngrok/mantle/icon";
import { FireIcon } from "@phosphor-icons/react/Fire";
} />
} />
} />
} />
```
## API Reference
The `Icon` accepts the following props:
| Prop | Type | Default | Description |
| ----------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `className` | `string` | | Specifies the element's CSS class name. See [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/className). |
| `style` | `React.CSSProperties` | | An object with CSS styles. See [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style). |
| `svg` | `ReactNode` | | A single SVG icon passed as a JSX tag. |
---
---
title: Icon Button
description: Initiates an action, such as completing a task or submitting information. Renders only a single icon as children with an accessible, screen-reader-only label.
---
# Icon Button
Initiates an action, such as completing a task or submitting information. Renders only a single icon as children with an accessible, screen-reader-only label.
```tsx
import { IconButton } from "@ngrok/mantle/button";
import { GlobeIcon } from "@phosphor-icons/react/Globe";
} />
} />
} />
} />
} />
} />
```
## isLoading
`isLoading` determines whether or not the icon button is in a loading state, default `false`. Setting `isLoading` will replace the icon with a spinner. It will also disable user interaction with the button and set `aria-disabled`.
```tsx
import { IconButton } from "@ngrok/mantle/button";
import { GlobeIcon } from "@phosphor-icons/react/Globe";
} />
} />
} />
} />
```
## Polymorphism
When you want to render *something else* as an `IconButton`, you can use the `asChild` prop to compose. This is useful when you want to splat the `IconButton` styling onto a `react-router` `Link`. Keep in mind that when you use `asChild` the `type` prop will **NOT** be passed to the child component.
```tsx
import { IconButton } from "@ngrok/mantle/button";
import { GlobeIcon } from "@phosphor-icons/react/Globe";
import { Link, href } from "react-router";
}>
;
```
## API Reference
The `IconButton` accepts the following props in addition to the [standard HTML button attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button).
| Prop | Type | Default | Description |
| ------------ | ------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appearance` | `"ghost"` \| `"outlined"` | `"outlined"` | Defines the visual style of the `IconButton`. |
| `asChild` | `boolean` | `false` | Use the `asChild` prop to compose the `IconButton` styling and functionality onto alternative element types or your own React components. |
| `icon` | `ReactNode` | | The icon to render inside the button. When `isLoading` is `true`, the icon is automatically replaced with a spinner. |
| `isLoading` | `boolean` | `false` | Determines whether or not the icon button is in a loading state. Setting `isLoading` will replace the icon with a spinner. It will also disable user interaction with the button and set `aria-disabled`. |
| `label` | `string` | | The accessible label for the icon. This label will be visually hidden but announced to screen reader users, similar to alt text for img tags. |
| `size` | `"xs"` \| `"sm"` \| `"md"` | `"md"` | The size of the `IconButton`. |
| `type` | `"button"` \| `"reset"` \| `"submit"` | | The default behavior of the `IconButton`. Unlike the native `button` element, unless you use the `asChild` prop, this prop is required and has no default value. |
---
---
title: Icons
description: Custom icons used throughout ngrok UI.
---
# Icons
A list of custom icons that are used throughout ngrok UI.
```tsx
import {
SortIcon,
TrafficPolicyFileIcon,
AutoThemeIcon,
ThemeIcon,
NgrokIcon,
} from "@ngrok/mantle/icons";
```
## API Reference
All custom icons are exported from `@ngrok/mantle/icons`.
### SortIcon
A sort direction indicator icon with different modes and directions.
| Prop | Type | Default | Description |
| :---------- | :------------------------------------------------------------ | :------ | :------------------ |
| `mode` | `"alphanumeric" \| "time"` | | The sorting mode. |
| `direction` | `"asc" \| "desc" \| "newest-to-oldest" \| "oldest-to-newest"` | | The sort direction. |
### TrafficPolicyFileIcon
The ngrok traffic policy file icon.
### AutoThemeIcon
An icon that automatically adapts to the current applied theme.
### ThemeIcon
An icon for a specific theme.
| Prop | Type | Default | Description |
| :------ | :------------------------------------------------------------------------------- | :------ | :-------------------- |
| `theme` | `"system" \| "light" \| "dark" \| "light-high-contrast" \| "dark-high-contrast"` | | The theme to display. |
### NgrokIcon
The ngrok logo icon.
## Icon Reference
This table is intentionally duplicated in plain markdown so readers (and AI agents) consuming the raw `.mdx` source can get full icon context without JSX rendering.
| Icon | Description |
| :---------------------- | :-------------------------------------------------------------------------------------------------- |
| `SortIcon` | Sort direction indicator icon with `mode` (`"alphanumeric"` or `"time"`) and `direction` variants. |
| `TrafficPolicyFileIcon` | The ngrok traffic policy file icon. |
| `AutoThemeIcon` | Icon that automatically adapts to the currently applied theme. |
| `ThemeIcon` | Theme-specific icon controlled by the `theme` prop (`"system"`, `"light"`, `"dark"`, and HC modes). |
| `NgrokIcon` | The ngrok logo icon. |
---
---
title: Input
description: Fundamental component for inputs.
---
# Input
Fundamental component for inputs.
```tsx
import { Input } from "@ngrok/mantle/input";
```
## Child Elements
You can compose additional visual or functional elements within the `Input` using `children`. The examples below show you how to render start and end icons or buttons. The [Password Input](/components/password-input) is built using this API under the hood! Keep in mind that you will need to manually pass the `InputCapture` component as children too because it is responsible for rendering the actual form `input` element! We provide an `InputCapture` component for you when you don't use the `children` API.
Note: when composing with interactive content (e.g. a `button`), you will need to consider whether or not that element should be tab-indexable or receive focus!
```tsx
import { Input, InputCapture } from "@ngrok/mantle/input";
import { Label } from "@ngrok/mantle/label";
import { InfoIcon } from "@phosphor-icons/react/Info";
import { MagnifyingGlassIcon } from "@phosphor-icons/react/MagnifyingGlass";
```
## When to use InputCapture
For 90% of cases — a simple input with optional icons or validation — pass children directly to `` (or omit children entirely). The `Input` renders an `InputCapture` for you internally, so you do not need to import or render it yourself.
`InputCapture` is the actual `` element wrapper. Reach for it only when composing custom adornments — for example, a left-side prefix and a right-side suffix where the existing `Input` slots don't fit. When you supply your own `children`, you must include `` so the form `input` element is still rendered. The [Password Input](/components/password-input) is built using this API under the hood.
## API Reference
The `Input` accepts the following props in addition to the [standard HTML input attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
| Prop | Type | Default | Description |
| ------------ | -------------------------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation` | `"error"` \| `"success"` \| `"warning"` \| `false` \| `() => "error" \| "success" \| "warning" \| false` | | Use the `validation` prop to show if the input has a specific validation status. This will change the border and outline of the input. The `false` type is useful when using short-circuiting logic so that you don't need to use a ternary with `undefined`. Setting `validation` to `error` also sets `aria-invalid`. |
---
---
title: Kbd
description: A square, centered keyboard "key" chip for rendering keyboard shortcut hints.
---
# Kbd
A square, centered keyboard "key" chip for rendering shortcut hints — `K`, `⌘`, `⌃`, `Enter`. Renders a native `` element so screen readers announce it as keyboard input. Sized so letters and modifier symbols share a consistent visual height, width, and baseline.
## When to use
- Documenting keyboard shortcuts in copy or tooltips.
- Inside menu items and command palettes alongside the action label.
- Inline with prose: "Press to open search."
## When not to use
- For arbitrary monospace text — use [`Code`](/components/code).
- For chord-style multi-key shortcuts as a single chip — render multiple `` elements separated by `+` text instead.
```tsx
import { Kbd } from "@ngrok/mantle/kbd";
K⌘⌃⇧⌥
;
```
## Keyboard Shortcuts
Combine multiple `Kbd` components to display keyboard shortcuts.
```tsx
import { Kbd } from "@ngrok/mantle/kbd";
⌘+K
;
```
## Accessibility
Symbol-only glyphs (`⌘`, `⌃`, `↵`) are not announced meaningfully by screen readers. Provide an accessible name via `aria-label` on the `` or include a visually-hidden label inside, and mark the visible glyph `aria-hidden`.
```tsx
import { Kbd } from "@ngrok/mantle/kbd";
⌘+K
;
```
## API Reference
### Kbd
The `Kbd` accepts the following props in addition to the [standard HTML kbd attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd).
| Prop | Type | Default | Description |
| :----------- | :---------- | :------ | :----------------------------------------------------------- |
| `children` | `ReactNode` | | The keyboard key or symbol to display (e.g., "K", "⌘", "⌃"). |
| `className?` | `string` | | Additional CSS classes to apply to the kbd element. |
---
---
title: Label
description: A Label represents a caption for an item in a user interface. Renders an accessible label associated with controls.
---
# Label
A caption for a form control — input, checkbox, radio, switch, select. Renders a native `