docs
Full documentation content.
\[!IMPORTANT]
>
> Variables default to `""` (empty) unless a fallback is documented below.
| var | description | example |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `MDX`\* | Path to `*.mdx` folder NB: can be relative or absolute | `docs` or `~/code/myproject/documentation` |
| `DOCS_CONFIG` | Path to the component and data configuration, relative to the current working directory or absolute. Unset: look for `docs.config.ts` there; no file keeps the built-in renderer. CLI: `--config` overrides this variable. | `config/docs.config.ts` |
| `NEXT_PUBLIC_LIBNAME`\* | Library name | `React Three Fiber` |
| `NEXT_PUBLIC_LIBNAME_SHORT` | Library short name | `r3f` |
| `BASE_PATH` | Base path for the final URL | `/react-three-fiber` |
| `DIST_DIR` | Path to the output folder ([within project](https://nextjs.org/docs/app/api-reference/next-config-js/distDir#:~:text=should%20not%20leave%20your%20project%20directory)) | `out` or `docs/out/react-three-fiber` |
| `OUTPUT` | Set to `export` for static output | `export` |
| `HOME_REDIRECT` | Where the home should redirect | `/getting-started/introduction` |
| `MDX_BASEURL` | Base URL for inlining relative images | `http://localhost:60141`or `https://github.com/pmndrs/react-three-fiber/raw/master/docs` |
| `SOURCECODE_BASEURL` | Base URL for `sourcecode:` code path | `https://github.com/pmndrs/react-three-fiber/tree/main` |
| `EDIT_BASEURL` | Base URL for displaying "Edit this page" URLs | `https://github.com/pmndrs/react-three-fiber/edit/master/docs` |
| `NEXT_PUBLIC_URL` | Final URL of the published website | `https://pmndrs.github.io/react-three-fiber` |
| `ICON` | Emoji or image to use as (fav)icon (path local to `MDX`) NB: also published at `/icon.svg`, which the libraries menu of the other pmndrs docs sites shows | `๐จ๐ญ` or `/icon.png` or `/favicon.ico` |
| `LOGO` | Logo src/path (either FQURL or local to `MDX` path) | `/logo.png` or `https://worldvectorlogo.com/r3f.png` |
| `GITHUB` | Github URL (its star count shows next to the header link) | `https://github.com/pmndrs/react-three-fiber` |
| `DISCORD` | Discord URL | `https://discord.com/channels/740090768164651008/740093168770613279` |
| `THEME_PRIMARY` | Primary accent color (unset: the pmndrs lime `#CAF543`) | `#323e48` |
| `THEME_SCHEME` | Theme scheme, moot while color match is on (see `THEME_COLOR_MATCH`) | `content` or `expressive` or `fidelity` or `monochrome` or `neutral` or `tonalSpot` or `vibrant` |
| `THEME_CONTRAST` | Theme contrast -- value between -1 and 1 | `0` or `-1` or `1` or `-.6` |
| `THEME_NOTE` | "note" color | `#1f6feb` |
| `THEME_TIP` | "tip" color | `#238636` |
| `THEME_IMPORTANT` | "important" color | `#8957e5` |
| `THEME_WARNING` | "warning" color | `#d29922` |
| `THEME_CAUTION` | "caution" color | `#da3633` |
| `THEME_CUSTOM_COLORS` | Custom colors of the theme, `name:hex[:blend]` comma-separated -- each a role, ``, `:blend` harmonizing it with the primary | `brand:#ff2d95:blend,status:#17b26a` |
| `THEME_COLOR_MATCH` | Each core and custom color rendered true to its own input (Material Theme Builder's "Color match"), on unless `false`: it takes precedence over `THEME_SCHEME`, readers can toggle it from the sidebar, and the scheme toggle is hidden while color match is on | `true` or `false` |
| `THEME_SECONDARY` | Secondary color, instead of the one derived from the primary | `#B03A3A` |
| `THEME_TERTIARY` | Tertiary color, instead of the one derived from the primary | `#7D5260` |
| `THEME_NEUTRAL` | Neutral color (the surfaces), instead of the pmndrs warm grey `#c1b793` | `#c1b793` |
| `THEME_NEUTRAL_VARIANT` | Neutral variant color (the tinted surfaces, outlines), instead of the pmndrs `#495720` | `#495720` |
| `THEME_ERROR` | Error color, instead of the pmndrs red `#FF4980` | `#FF4980` |
| `THEME_STORYBOOK` | "storybook" color | `#ff4785` |
| `THEME_NPM` | "npm" color | `#cb3837` |
| `THEME_CHROMATIC` | "chromatic" color | `#fc521f` |
| `CONTRIBUTORS_PAT` | GitHub token for contributors API and the header star count (see: ) | `ghp_1234567890` |
| `LIB_VERSION` | Version label in the sidebar footer (default: `git describe --tags`) | `v1.2.3` |
| `TAG_MATCH` | Tags the version label is read from (`git describe --match`) | `v*.*.*` |
| `VERSION_URL_TEMPLATE` | URL of a branch deployment; enables the [version switcher](#version-switcher) | `https://mylib-git-{branch:vercel}-myteam.vercel.app` |
| `VERSION_PRODUCTION_BRANCH` | Production branch (default: `main`) | `master` |
| `VERSION_PRODUCTION_URL` | Production branch URL (default: `NEXT_PUBLIC_URL`) | `https://docs.pmnd.rs` |
| `VERSION_BRANCHES` | Regex of the branches the switcher offers | `^(main\|next\|v\d+)$` |
| `VERSION_BRANCHES_LIST` | Branches the switcher offers, instead of the remote's | `main,next,v9` |
\* Required
Readers can re-seed `THEME_PRIMARY`, `THEME_SCHEME`, `THEME_CONTRAST` and `THEME_COLOR_MATCH` from the theme controls in the sidebar. An overridden control is outlined, and a double-click (or Delete) brings the site's value back.
`MDX_BASEURL`
Given a `advanced/introduction.mdx` file in the `MDX` folder:
```md title="advanced/introduction.mdx"

```
becomes (for a `MDX_BASEURL=http://localhost:60141` value):
```md

```
`http://localhost:60141` being the `MDX` folder served.
> \[!TIP] When deployed on GitHub Pages, `MDX_BASEURL` will typically value something like `https://github.com/pmndrs/uikit/raw/main/docs`, thanks to [`build.yml`](.github/workflows/build.yml) rule.
`THEME_*`
We implement [m3 design system](https://m3.material.io/styles/color/system/overview), using [material-theme-builder](https://github.com/abernier/material-theme-builder).
[material-theme-builder color scheme](https://main--695eb517cb602e59b4cc045c.chromatic.com/iframe.html?id=mtb--st-1\&viewMode=story)
This is an embed iframe of [material-theme-builder's `` story](https://main--695eb517cb602e59b4cc045c.chromatic.com/?path=/story/mtb--st-1).
> \[!NOTE]
>
> - [Material Color](https://www.youtube.com/playlist?list=PLsoLz-E4Os4WWkrvRuQ7BJuVF-WfOyfWT) for more information
> - The defaults are [pmndrs/design-system](https://github.com/pmndrs/design-system)'s palette: the lime `#CAF543` under color match, a warm grey neutral, and seven colors every site gets as custom colors, `lime`, `teal`, `cyan`, `purple`, `red`, `orange` and `yellow` (``).
> - `THEME_PRIMARY` is the seed: the secondary and tertiary colors are derived from it, unless the site gives its own (`THEME_SECONDARY`, `THEME_TERTIARY`). The neutral, neutral variant and error colors are the pmndrs ones, unless the site gives its own (`THEME_NEUTRAL`, `THEME_NEUTRAL_VARIANT`, `THEME_ERROR`).
> - `THEME_COLOR_MATCH` (on unless `"false"`) is [Material Theme Builder](https://material-foundation.github.io/material-theme-builder/)'s "Color match - Stay true to my color inputs": each core and custom color keeps its input's chroma instead of being toned down to the scheme's, so `THEME_SCHEME` is ignored and the scheme toggle is hidden while color match is on. Readers can toggle color match from the sidebar. A custom color with `:blend` is still harmonized first.
`VERSION_*`, `LIB_VERSION`, `TAG_MATCH`
- Label: `git describe --tags` (eg. `v10.7.9`, `v10.7.9-3-ga1b2c3d`), or `@` without tags. `LIB_VERSION` overrides it.
- `TAG_MATCH` narrows the tags:
| repo tags | `TAG_MATCH` |
| -------------------------- | ----------- |
| `v10.7.9`, `v10` | `v*.*.*` |
| `leva@0.10.1`, `other@1.0` | `leva@*` |
- `VERSION_URL_TEMPLATE` enables the switcher: the label lists the branches, each leading to the same page on its deployment. Full public URL (base path included), with a placeholder:
| placeholder | `feat/Dark_mode` becomes | for | example |
| ----------------- | ------------------------ | ---------------------- | ----------------------------------------------------- |
| `{branch}` | `feat-dark-mode` | URLs you create | `https://docs-git-{branch}-pmndrs.vercel.app` |
| `{branch:vercel}` | `feat-darkmode` | Vercel Git integration | `https://mylib-git-{branch:vercel}-myteam.vercel.app` |
| `{branch:raw}` | `feat%2FDark_mode` | a path or query string | `https://example.com/preview?branch={branch:raw}` |
- A hostname label holds 63 characters at most. When a long branch would overflow it, `{branch:vercel}` follows Vercel's own truncation, and `{branch}` keeps its slug's first `63 - (rest of the label) - 7` characters, drops a trailing `-`, and appends `-` and the first 6 hex characters of the branch name's SHA-256 (`printf %s "$branch" | sha256sum | cut -c1-6`): eg. `claude/pages-shadcn-tailwind-prep-98d23c` in `https://design-system-git-{branch}-pmndrs.vercel.app` becomes `design-system-git-claude-pages-shadcn-tailwind-pr-2675d4-pmndrs`. Outside the hostname, `{branch}` is never cut. To alias a deployment there, `pmndrs-docs version-url` prints the URL (`--hostname`: the hostname alone), or see the reusable workflow's [`version_hostname` output](/github-actions/introduction#version-switcher).
- `VERSION_PRODUCTION_BRANCH` leads to `VERSION_PRODUCTION_URL`; every other branch shows a banner linking to production.
- Branches: the remote's, minus `dependabot/`, `renovate/`, `changeset-release/`. `VERSION_BRANCHES` replaces that filter, `VERSION_BRANCHES_LIST` the whole list. Read at build time.
> \[!IMPORTANT]
>
> - The per-branch URLs are yours to create: Vercel's Git integration does, `vercel deploy` does not (`vercel alias set`, see [`ci.yml`](https://github.com/pmndrs/docs/blob/main/.github/workflows/ci.yml)).
> - Git needs full history and tags (`fetch-depth: 0`).
## Usage
### dev
This is [`dev.sh`](https://github.com/pmndrs/docs/blob/main/dev.sh). The site listens on `PORT` (default `3000`), the MDX folder on any free port, so several checkouts can run side by side:
```sh collapsible
$ (
trap 'kill -9 0' SIGINT
export PORT=${PORT:-3000}
export _PORT=$(node -e 'const s = require("net").createServer().listen(0, () => { console.log(String(s.address().port)); s.close() })')
export MDX=docs
export NEXT_PUBLIC_LIBNAME="docs"
export NEXT_PUBLIC_LIBNAME_SHORT="docs"
export BASE_PATH=
export DIST_DIR=
export OUTPUT=
export HOME_REDIRECT=
export MDX_BASEURL=http://localhost:$_PORT
export SOURCECODE_BASEURL="vscode://file$(pwd)"
export EDIT_BASEURL="vscode://file$(pwd)/docs"
export NEXT_PUBLIC_URL=
export ICON=
export LOGO=gutenberg.jpg
export GITHUB=https://github.com/pmndrs/docs
export DISCORD=https://discord.com/channels/740090768164651008/1264328004172255393
export CONTRIBUTORS_PAT=
npx serve $MDX -p $_PORT --no-port-switching --no-clipboard &
pnpm run dev &
wait
)
```
Then go to:
When running the engine against another repository, set both `MDX` and `DOCS_CONFIG`; the configuration is discovered from the working directory, not the MDX folder:
```sh
MDX=/path/to/library/docs DOCS_CONFIG=/path/to/library/docs.config.ts pnpm dev
```
> \[!TIP] If `HOME_REDIRECT=` empty, `/` will not redirect, and instead displays an index of libraries.
### build
This is [`start.sh`](https://github.com/pmndrs/docs/blob/main/start.sh), same ports:
```sh collapsible
$ (
trap 'kill -9 0' SIGINT
# `next dev` leaves types pointing at `src/app/api`, which the export build moves aside
rm -rf out .next/dev/types
export PORT=${PORT:-3000}
export _PORT=$(node -e 'const s = require("net").createServer().listen(0, () => { console.log(String(s.address().port)); s.close() })')
export MDX=docs
export NEXT_PUBLIC_LIBNAME="docs"
export NEXT_PUBLIC_LIBNAME_SHORT="docs"
export BASE_PATH=
export DIST_DIR=
export OUTPUT=export
export HOME_REDIRECT=
export MDX_BASEURL=http://localhost:$_PORT
export SOURCECODE_BASEURL=
export EDIT_BASEURL=
export NEXT_PUBLIC_URL=
export ICON=
export LOGO=gutenberg.jpg
export GITHUB=https://github.com/pmndrs/docs
export DISCORD=https://discord.com/channels/740090768164651008/1264328004172255393
export CONTRIBUTORS_PAT=
pnpm run build
npx serve $MDX -p $_PORT --no-port-switching --no-clipboard &
npx serve out -p $PORT &
wait
)
```
### CLI
The published CLI builds without cloning the engine. If your custom components import packages, install those dependencies in your repository first:
```sh
$ cd ~/code/pmndrs/react-three-fiber
$ (
trap 'kill -9 0' SIGINT
export PORT=${PORT:-3000}
export _PORT=$(node -e 'const s = require("net").createServer().listen(0, () => { console.log(String(s.address().port)); s.close() })')
export NEXT_PUBLIC_LIBNAME="React Three Fiber"
export NEXT_PUBLIC_LIBNAME_SHORT="r3f"
export BASE_PATH=/toto
export HOME_REDIRECT=/getting-started/introduction
export MDX_BASEURL=http://localhost:$_PORT
export ICON=๐จ๐ญ
export GITHUB=https://github.com/pmndrs/react-three-fiber
npx -y @pmndrs/docs@latest build docs docs/out --format website
npx serve docs -p $_PORT --no-port-switching --no-clipboard &
npx -y serve docs/out -p $PORT &
wait
)
```
Then go to:
`npx @pmndrs/docs@latest build --help` lists the CLI flags, including `--config` for a configuration outside the repository root.
Both `--format website` and `--format fragment` use the same Next.js build and static export. Fragments contain only the compiled document: no layout, stylesheet or script. The CLI copies assets alongside folder fragments and preserves static Mermaid and Sandpack content.
```sh
$ npx @pmndrs/docs@latest build page.mdx
$ cat page.mdx | npx @pmndrs/docs@latest build
```
A single file or stdin writes HTML to stdout and build logs to stderr. These inputs also run a full Next.js build. Consumer components and data use the same `docs.config.ts` as the website.
## Agents
`llms.txt` dumps and the pmndrs MCP server moved to their own page: [Agents](/agents/introduction).
]]> \[!TIP]
>
> `title` and `description` support markdown:
>
> ```yaml
> ---
> title: "`Intro` component"
> description: Introduction component for **documentation** pages with `code` and [links](#test)
> ---
> ```
>
> โ ๏ธ If starting with backtick, eg ``title: "`Intro` component"`` wrap in quotes!
## MARKDOWN
Worth mentioning, you can use **standard HTML**, with [tailwind](https://tailwindcss.com) support:
```md
This is an embed iframe of [Drei's `` story](https://drei.pmnd.rs/?path=/docs/staging-accumulativeshadows--docs).
```
result:
This is an embed iframe of [Drei's `` story](https://drei.pmnd.rs/?path=/docs/staging-accumulativeshadows--docs).
> \[!TIP]
>
> We use [material-theme-builder](https://github.com/abernier/material-theme-builder) colors, eg: `text-on-surface-variant` above
Plus:
- any [GitHub Flavored Markdown](https://github.github.com/gfm/) syntax.
- all exported [`components/mdx/index.tsx`](components/mdx/index.tsx) MDX components.
See the following pages for detailed documentation on each custom component.
To register your own React components and shared data for MDX pages, see [Custom components and data](custom-components).
]]>
This is an intro. Here you can write a long description of the article, longer than the frontmatter `description`, eventually with [links](#), lists, etc.
Multiple paragraphs are also possible, or anything.
```
result:
This is an intro. Here you can write a long description of the article, longer than the frontmatter `description`, eventually with [links](#), lists, etc.
Multiple paragraphs are also possible, or anything.
]]>First itemSecond **item**
```
result:
## What you'll learn
- First item
- Second **item**
]]> \[!TIP] You are encouraged to use relative images, since dimensions are automatically added as `img[width][height]` attributes.
>
> For others, think about adding them to **prevent layout shift**.
## Dimensions
It is also possible to specify only one dimension (other is inferred from intrinsic ratio), eg:
```md
```
result:

## Sizing with CSS
Although it is probably better to use CSS for this:
```md
```
result:

> \[!NOTE] See [`MDX_BASEURL`](/getting-started/introduction#configuration:~:text=MDX_BASEURL) to understand how it is converted to a full URL.
]]>Hello, {who}!
)
}
```
````
result:
```tsx {1,4-6} showLineNumbers
type Props = {who: string}
function Hi({who}: Props) {
return (
Hello, {who}!
)
}
```
## Starting line number
You can start at a specific `X` line number `showLineNumbers=X`:
````md
```tsx {1,4-6} showLineNumbers=150
type Props = {who: string}
function Hi({who}: Props) {
return (
Hello, {who}!
)
}
```
````
result:
```tsx {1,4-6} showLineNumbers=150
type Props = {who: string}
function Hi({who}: Props) {
return (
Hello, {who}!
)
}
```
## Diff
`diff` format is supported:
````md
```diff
diff --git a/example.txt b/example.txt
index 8c3317a..47ea956 100644
--- a/example.txt
+++ b/example.txt
@@ -1,4 +1,4 @@
-Hello, World!
+Hello, GitHub World!
Here are some changes:
-This line will be modified.
+This line has been modified.
This line will stay the same.
```
````
result:
```diff
diff --git a/example.txt b/example.txt
index 8c3317a..47ea956 100644
--- a/example.txt
+++ b/example.txt
@@ -1,4 +1,4 @@
-Hello, World!
+Hello, GitHub World!
Here are some changes:
-This line will be modified.
+This line has been modified.
This line will stay the same.
```
## Package managers
npm commands in a `bash`, `sh` or `shell` block get a tab per package manager:
````md
```bash
npm install three
```
````
result:
```bash
npm install three
```
]]>
B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug]
D --> B
C --> E[End]
```
````
result:
```mermaid
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug]
D --> B
C --> E[End]
```
---
````md
```mermaid
sequenceDiagram
participant User
participant Browser
User->>Browser: Click button
Browser-->>User: Display result
```
````
result:
```mermaid
sequenceDiagram
participant User
participant Browser
User->>Browser: Click button
Browser-->>User: Display result
```
---
[KaTeX support](https://github.com/mermaid-js/mermaid/pull/2885):
````md
```mermaid
graph LR
A["$$f(\relax{x}) = \int_{-\infty}^\infty \hat{f}(\xi)\,e^{2 \pi i \xi x}\,d\xi$$"] -->|"$$\Bigg(\bigg(\Big(\big((\frac{-b\pm\sqrt{b^2-4ac}}{2a})\big)\Big)\bigg)\Bigg)$$"| B("$$1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots}}}}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\phase{-78^\circ}$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
````
result:
```mermaid
graph LR
A["$$f(\relax{x}) = \int_{-\infty}^\infty \hat{f}(\xi)\,e^{2 \pi i \xi x}\,d\xi$$"] -->|"$$\Bigg(\bigg(\Big(\big((\frac{-b\pm\sqrt{b^2-4ac}}{2a})\big)\Big)\bigg)\Bigg)$$"| B("$$1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots}}}}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\phase{-78^\circ}$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
]]> \[!CAUTION]
>
> Deprecated, use [tailwind `grid-cols-*`](https://tailwindcss.com/docs/grid-template-columns) instead.
Responsive grid layout.
```md
A
B
C
D
E
F
G
```
result:
- A
- B
- C
- D
- E
- F
- G
]]> \[!NOTE] See Sandpack [official documentation](https://sandpack.codesandbox.io/docs/getting-started/usage) for full usage.
> \[!WARNING] `three` resolving to `0.186.0` or higher (`latest` included) is pinned to `0.185.1` automatically: since `0.186.0`, its CommonJS entry calls `process.emitWarning`, which Sandpack's in-browser bundler does not provide (the preview fails with `process.emitWarning is not a function`).
```tsx
)
}`,
}}
/>
```
result:
Interactive code example (available on the documentation website).
> \[!TIP] Sandpack UI SSR-rendering[^1] is also supported (out of the box).
#### `Sandpack[folder]`
Instead of `files`, a `folder` prop allows you to pass a folder containing all the files:
```tsx
```
`folder` path is relative to the `.mdx` file:
```text
|-- getting-started
| |-- authoring.mdx <= relative to this article
| `-- authoring-sandpack-cloud <= the folder
```
> \[!TIP] It will simply:
>
> - build the `files` prop for you (including all `.{js|ts|jsx|tsx|css}` it finds)
> - build `customSetup.dependencies` from `package.json` if it exists
result:
Interactive code example (available on the documentation website).
---
It is also possible to pass some individual [file format](https://sandpack.codesandbox.io/docs/getting-started/usage#file-format) configuration:
```tsx
```
result:
Interactive code example (available on the documentation website).
#### `Sandpack[fileExplorer]`
```tsx
```
result:
Interactive code example (available on the documentation website).
> \[!TIP] Conveniently, enabling `fileExplorer` will by default `[codeEditor={showTabs: false}]`. You can override it with [`[codeEditor]` options](#sandpack%5Bcodeeditor%5D)
---
You can also pass [any `fileExplorer={options: ComponentProps}`](https://sandpack.codesandbox.io/docs/advanced-usage/components#file-explorer:~:text=Preview-,Options,-If%20you%27re%20looking).
#### `Sandpack[codeEditor]`
You can pass [any `codeEditor={options: ComponentProps}`](https://sandpack.codesandbox.io/docs/advanced-usage/components#code-editor:~:text=Preview-,Options,-Extensions).
#### `Sandpack[codeViewer]`
It will render the [`SandpackCodeViewer`](https://sandpack.codesandbox.io/docs/advanced-usage/components#code-viewer) instead of the default [`SandpackCodeEditor`](https://sandpack.codesandbox.io/docs/advanced-usage/components#code-editor).
```tsx
```
result:
Interactive code example (available on the documentation website).
You can also pass [any `codeViewer={options: ComponentProps}`](https://sandpack.codesandbox.io/docs/advanced-usage/components#code-viewer:~:text=Preview-,Options,-CodeMirror%20decorations).
##### `Sandpack[codeViewer][filesDecorators]`
You can define per-file [decorators](https://sandpack.codesandbox.io/docs/advanced-usage/components#codemirror-decorations) through `filesDecorators` prop:
```tsx
```
result:
Interactive code example (available on the documentation website).
#### `Sandpack[preview]`
You can pass [any `preview={options: ComponentProps}`](https://sandpack.codesandbox.io/docs/advanced-usage/components#preview:~:text=Preview-,Options,-Additional%20buttons).
[^1]:
]]>`](img), it can be relative to the article:
```text {2}
|-- authoring
| |-- codesandbox.mdx <= relative to this article
| `-- cell-fracture.webp
```
```md
```
result:
[Cell fracture](https://codesandbox.io/s/3rjsl)
> \[!TIP] A relative `img` gets its dimensions added automatically, just as a relative [`Img`](img) does. A full URL (`img="https://โฆ"`) works too, as is.
The same image is the page's thumb in the [`Entries`](entries) listing.
## Without `img`
CodeSandbox no longer serves screenshots of sandboxes, so without `img` a placeholder stands in, still linking to the sandbox:
```md
```
result:
[Cell fracture](https://codesandbox.io/s/3rjsl)
Optional metadata can accompany either an image or a placeholder:
```md
```
[Cell fracture](https://codesandbox.io/s/3rjsl)
Explore a physics example with Rapier.
With only an `id`, the placeholder link is still labelled `CodeSandbox` followed by that id.
> \[!NOTE] `screenshot_url` is the former name of `img`. It still works, but is deprecated.
## Props
| Prop | Type | Description |
| ------------- | ---------- | --------------------------------------------------------------- |
| `id` | `string` | Sandbox id, e.g. `3rjsl` for `https://codesandbox.io/s/3rjsl` |
| `title` | `string` | Caption under the preview |
| `img` | `string` | Preview image: relative to the article, or a full URL |
| `description` | `string` | Text under the title |
| `tags` | `string[]` | Tags under the title, e.g. `tags={["physics", "rapier"]}` |
| `embed` | `boolean` | Embeds the live sandbox in an `iframe` instead of linking to it |
]]>Highlights information that users should take into account, even when skimming.
```
or better:
```md
> [!NOTE]
> Highlights information that users should take into account, even when skimming.
```
result:
> \[!NOTE] Highlights information that users should take into account, even when skimming.
---
We also support: `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]`
result:
> \[!TIP] Optional information to help a user be more successful.
> \[!IMPORTANT] Crucial information necessary for users to succeed.
> \[!WARNING] Critical content demanding immediate user attention due to potential risks.
> \[!CAUTION] Negative potential consequences of an action.
---
The `title` prop overrides the label derived from `keyword` (icon and background still follow the keyword). Only available via the JSX form:
```md
Browse these docs from your MCP-compatible client.
```
result:
> \[!TIP] **MCP-server**
>
> Browse these docs from your MCP-compatible client.
Rich Markdown also works inside a callout:
> \[!NOTE] Keep **headings, links and lists** in your existing authoring content:
>
> - Read the [Keypoints](keypoints) before starting.
> - Open the [sandbox examples](codesandbox) to try the ideas.
Custom labels wrap in full, and rich blocks retain their structure:
> \[!TIP] **Before sharing a sandbox, check its dependencies and document the environment needed to reproduce the example**
>
> ### Reproduce the example
>
> Run the example locally before sharing it:
>
> ```sh
> pnpm install
> pnpm dev
> ```
>
> - Document the requirements.
> - Include the supported Node.js version.
> - Link to the [sandbox examples](codesandbox).
]]>storybook
suspenseDom only
```
result:
[storybook](https://drei.pmnd.rs/?path=/story/abstractions-positionalaudio--positional-audio-scene-st) [suspense](https://r3f.docs.pmnd.rs/api/hooks#useloader) Dom only
A badge is inline, like a link: badges on consecutive lines make one row, and a badge sits in a sentence too, as in "works with suspense".
## Color
`color` takes a color role of the theme: `primary`, `secondary`, `tertiary`, `error`, or one of the custom colors `note`, `tip`, `important`, `warning`, `caution`; or a brand color, `storybook`, `npm`, `chromatic`, shown as is, with white text, in light and dark alike (see `THEME_*` in [configuration](/getting-started/introduction#configuration)):
```md
primarysecondarytertiaryerrornotetipimportantwarningcautionstorybooknpmchromaticsuspense
```
result:
primary secondary tertiary error note tip important warning caution storybook npm chromatic [suspense](https://r3f.docs.pmnd.rs/api/hooks#useloader)
## Label and logo
`label` adds a de-emphasized label before the text, and `logo` a brand logo, by its [simple-icons](https://simpleicons.org) slug โ for now `storybook`, `chromatic`, `discord`, `github` or `npm`; any other shows none:
```md
Open in Codespaces
```
result:
[GitHub Open in Codespaces](https://github.com/codespaces/new?template_repository=pmndrs%2Fdrei)
## shields.io images
A [shields.io](https://shields.io) image is an image like any other: it is not turned into a `Badge`. For a badge that follows the theme, write ``.
]]> \[!CAUTION] Deprecated, use [`Gha[keyword="NOTE"]`](gha) instead.
Existing `Hint` markup still renders the same static note through the shadcn Card composition.
```md
I'm a deprecated hint. Use `Gha` instead.
```
result:
> \[!NOTE]
>
> I'm a deprecated hint. Use `Gha` instead.
]]>
```
> \[!WARNING] [`CONTRIBUTORS_PAT`](introduction#configuration:~:text=CONTRIBUTORS_PAT) needs to be set. Otherwise, it will display John Doe.
result:
[Contributors](https://github.com/pmndrs/docs/graphs/contributors)
]]>
```
result:
[Backers](https://opencollective.com/react-three-fiber#backers)
]]>
```
result:
- [pmndrs/docs](/getting-started/introduction)
- [Authoring](/authoring/introduction)
- [\`Intro\`](/authoring/intro)
- [\`Keypoints\`](/authoring/keypoints)
- [\`Img\`](/authoring/img)
- [\`Code\`](/authoring/code)
- [Mermaid](/authoring/mermaid)
- [\`Grid\` (Deprecated)](/authoring/grid)
- [\`Sandpack\`](/authoring/sandpack)
- [\`Codesandbox\`](/authoring/codesandbox)
- [\`Gha\`](/authoring/gha)
- [\`Badge\`](/authoring/badge)
- [\`Hint\` (Deprecated)](/authoring/hint)
- [\`Contributors\`](/authoring/contributors)
- [\`Backers\`](/authoring/backers)
- [\`Entries\`](/authoring/entries)
- [Reusable GitHub Actions workflow](/github-actions/introduction)
- [Agents](/agents/introduction)
- [\`Steps\`](/authoring/steps)
- [\`Tabs\`](/authoring/tabs)
- [\`Color\`](/authoring/color)
- [Custom components and data](/authoring/custom-components)
> \[!TIP] When a page includes a [``](codesandbox) component, the Entries listing will automatically display its [`img`](codesandbox) as a thumb next to that page's link, or a CodeSandbox icon when it has none.
]]>
```
## MCP Server
[MCP server requests, live](https://docs.pmnd.rs/mcp/live)
`llms-full.txt` is a whole documentation site in one file โ too big to paste into an agent for a one-line question. So [docs.pmnd.rs](https://docs.pmnd.rs) also exposes it as an [MCP](https://modelcontextprotocol.io) server, which lets an agent read a table of contents first, then fetch the one page it needs.
One endpoint for all libraries, over streamable HTTP: `https://docs.pmnd.rs/api/mcp`. It exposes:
| kind | name | what it gives |
| -------- | ------------------------ | --------------------------------------------------------------------------- |
| resource | `docs://pmndrs/manifest` | which libraries are served, and how to query them |
| resource | `docs://{lib}/index` | that library's pages, one `{path} - {title}` per line |
| tool | `get_page_content` | the markdown of a single page, given a `lib` + a `path` from the index |
| resource | `examples://index` | the [example gallery](https://pmndrs.github.io/examples), one line per demo |
| tool | `get_example` | one demo in full, given a `name` from that index |
A typical round-trip โ _"how do I use TypeScript with Zustand?"_:
```txt
read docs://zustand/index
โ [...] /learn/guides/beginner-typescript - Beginner TypeScript Guide [...]
call get_page_content(lib="zustand", path="/learn/guides/beginner-typescript")
โ the page, as markdown
```
Two pages transferred instead of the whole site.
### Examples
The docs say what an API takes. Most three.js questions are _how is this put together_, and the [gallery](https://pmndrs.github.io/examples) has 167 demos that answer it โ so the same endpoint serves them, on the same read-the-index-then-fetch shape.
```txt
read examples://index
โ [...] caustics ยท #transmission [...]
diamond-refraction ยท Diamon refraction material ยท +postprocessing,leva ยท #refraction,bvh
call get_example(name="caustics")
โ the demo: its source, its live URL, its asset attribution, and the exact
dependency versions the code is written against
```
An index line drops whatever the demo does not carry, so it costs what it is worth. `+` lists only what a demo uses on top of `@react-three/fiber` and `@react-three/drei`, which all of them use. A trailing `~23k` is what `get_example` will cost in tokens โ only eight demos carry one, and its absence means the demo is small enough to open without weighing it.
An example is a snapshot, not a spec: it pins its own versions, `get_example` reports them, and an answer that turns on a current signature still belongs in the docs above.
> \[!NOTE]
>
> These documents are published, not rendered here โ `pmndrs/examples` writes them at build time, at the page URL with an extension on the end:
>
> | | |
> | ----------------------- | --------------------------------------------------- |
> | `/examples/caustics` | the page |
> | `/examples/caustics.md` | the same example, as `get_example` returns it |
> | `/llms.txt` | the whole gallery, as `examples://index` returns it |
>
> Each page links its own, so it is found either by looking or by guessing:
>
> ```html
>
> ```
>
> The demos themselves โ `/caustics`, which is what the READMEs' badges point at โ carry the same links plus a `canonical` back to the page.
>
> There is no `llms-full.txt`: concatenated, the gallery is \~378k tokens, against \~50k for a documentation site. The index and one example is the whole point.
> \[!TIP]
>
> [`pmndrs/claude-code-plugin`](https://github.com/pmndrs/claude-code-plugin) supports it natively
>
> ```sh
> /plugin marketplace add pmndrs/claude-code-plugin
> /plugin install pmndrs@pmndrs
> ```
For other clients, see the JSON config in the [MCP-server](https://docs.pmnd.rs) box on the home page. The server is also published to the official [MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.pmndrs/docs) as `io.github.pmndrs/docs`, from the [`server.json`](https://github.com/pmndrs/docs/blob/main/server.json) at the root of this repository, so clients that browse the registry find it there. To poke at it by hand โ see the resources, call the tool, read the raw JSON-RPC โ run the official inspector:
```sh
$ HOST=127.0.0.1 npx -y @modelcontextprotocol/inspector
```
In the web UI it opens: **Add Servers** โบ **Add manually**, transport `streamable-http`, URL `https://docs.pmnd.rs/api/mcp` (or your own `http://localhost:3000/api/mcp`), and flip the server's toggle to connect.

### Getting your library served
Only libraries publishing a `/llms-full.txt` dump are served โ the ones badged `MCP` on [docs.pmnd.rs](https://docs.pmnd.rs). The examples gallery is not one of them; it publishes its own catalog, and the two resources above are wired to it directly. To join the libraries:
1. **Publish the dump.** If your site is already built with this generator, bump it to a version shipping `/llms-full.txt` and redeploy. Otherwise, either migrate to this generator, or emit that file yourself with the same XML shape (`โฆ`).
> \[!TIP]
>
> With the [reusable workflow](/github-actions/introduction), pin the major tag โ it always resolves to the latest `v3.x`:
>
> ```yml
> uses: pmndrs/docs/.github/workflows/build.yml@v3
> ```
>
> `/llms-full.txt` ships since [`v3.2.0`](https://github.com/pmndrs/docs/releases/tag/v3.2.0), in its MCP-ready shape since [`v3.4.1`](https://github.com/pmndrs/docs/releases/tag/v3.4.1).
2. **Check it.** `curl -sI /llms-full.txt` must return `200`, and the body must contain `` entries. A missing dump makes the library's index resource fail loudly, on purpose.
3. **Flip the flag.** Open a PR on [pmndrs/docs](https://github.com/pmndrs/docs) setting `llms_full: true` on your entry in `libs` ([`src/app/page.tsx`](https://github.com/pmndrs/docs/blob/main/src/app/page.tsx)). That single flag drives the `MCP` badge, the `docs://{lib}/index` resource and the `get_page_content` enum. It takes effect once [docs.pmnd.rs](https://docs.pmnd.rs) is redeployed โ every push to `main` does that.
> \[!NOTE]
>
> This is a one-off. Once served, **content** updates need no redeploy on either side: pages are fetched at request time and revalidated every 5 minutes.
]]>Install the dependencies
```sh
npm install three @react-three/fiber
```
Create a scene
Render a `Canvas`, with a mesh in it:
```tsx
import { Canvas } from '@react-three/fiber'
export default function App() {
return (
)
}
```
Run it
That's it: start your dev server.
````
result:
### Install the dependencies
```sh
npm install three @react-three/fiber
```
### Create a scene
Render a `Canvas`, with a mesh in it:
```tsx
import { Canvas } from '@react-three/fiber'
export default function App() {
return (
)
}
```
### Run it
That's it: start your dev server.
]]>ReactVueSvelte
| Aspect | React |
| --- | --- |
| Type | Library |
| Templates | JSX |
| Reactivity | Re-render on state change |
| Aspect | Vue |
| --- | --- |
| Type | Framework |
| Templates | Single-file components with HTML templates |
| Reactivity | Reactive proxies |
| Aspect | Svelte |
| --- | --- |
| Type | Compiler |
| Templates | `.svelte` files |
| Reactivity | Compiled at build time |
```
result:
**React**
**Vue**
**Svelte**
| Aspect | React |
| ---------- | ------------------------- |
| Type | Library |
| Templates | JSX |
| Reactivity | Re-render on state change |
| Aspect | Vue |
| ---------- | ------------------------------------------ |
| Type | Framework |
| Templates | Single-file components with HTML templates |
| Reactivity | Reactive proxies |
| Aspect | Svelte |
| ---------- | ---------------------- |
| Type | Compiler |
| Templates | `.svelte` files |
| Reactivity | Compiled at build time |
Same API as [shadcn `Tabs`](https://ui.shadcn.com/docs/components/base/tabs).
`defaultValue` is required (otherwise no tab is shown until hydration).
Markdown inside a `TabsContent` (a table, a code block, a list) needs a blank line after the opening tag and before the closing one.
## Rich triggers
Triggers accept any markup:
```md
React 19Vue
| Aspect | React |
| --- | --- |
| Version | 19 |
| Released | December 2024 |
| Aspect | Vue |
| --- | --- |
| Version | 3 |
| Released | September 2020 |
```
result:
**React 19**
**Vue**
| Aspect | React |
| -------- | ------------- |
| Version | 19 |
| Released | December 2024 |
| Aspect | Vue |
| -------- | -------------- |
| Version | 3 |
| Released | September 2020 |
## Synced tabs
Tabs of the same `syncKey` switch together: picking a tab in one picks it in every other, on this page and on the others. The pick is remembered (in `localStorage`), after a reload and in other browser tabs.
```md
ReactVueSvelte
Templates are JSX: markup in JavaScript.
Templates are HTML, in single-file components.
Templates are HTML, in `.svelte` files.
ReactSvelte
State changes re-render the component.
State changes are compiled into DOM updates.
```
result:
**React**
**Vue**
**Svelte**
Templates are JSX: markup in JavaScript.
Templates are HTML, in single-file components.
Templates are HTML, in `.svelte` files.
**React**
**Svelte**
State changes re-render the component.
State changes are compiled into DOM updates.
A `Tabs` without the picked tab stays as it is: pick Svelte above, then Vue, and the second one stays on Svelte. The pick is still Vue, for the next `Tabs` that has it. A page loaded with a pick none of its tabs have shows their `defaultValue`.
The page's HTML shows `defaultValue`: the picked tab replaces it once the page's JavaScript runs. A link to a `#hash` in a hidden panel opens its tab as a click would, so it picks that tab too.
]]>
```
result:
`primary`
The role is any Material role of the theme, `primary`, `surface-container-high`, `on-tertiary-fixed-variant`โฆ or one of its custom colors (see `THEME_*` in [configuration](/getting-started/introduction#configuration)) -- the seven pmndrs ones, `lime`, `teal`, `cyan`, `purple`, `red`, `orange` and `yellow`, the alert ones, and the site's own too, from `THEME_CUSTOM_COLORS`: `brand:#ff2d95` there makes `` here, following the scheme, contrast and primary like any role. A color is inline, like a badge: colors on consecutive lines make one row, and a color sits in a sentence too.
```md
```
result:
`lime` `teal` `cyan` `purple` `red` `orange` `yellow` `note` `tip` `important` `warning` `caution`
`size` sets the diameter, `sm`, `md` (the default) or `lg`.
## Cells
`variant="cell"` makes it a cell, a rounded square of `size` with the role's name on it in the color Material pairs it with, as on Material's scheme poster. The label is the role in Title Case; `children` replaces it. `ink` picks another text role.
```md
```
result:
`inverse-primary`
## Groups
A `ColorGroup` fuses cells into one rounded block, `horizontal` (side by side, the default) or `vertical` (stacked), each cell filling its column. In a group, a cell says so, `variant="cell"`, or `variant="on"` for the strip of the role that goes on it:
```md
```
result:
`primary` `on-primary`
Groups nest, a horizontal one as a row of a vertical one:
```md
```
result:
`secondary-fixed` `secondary-fixed-dim`
`on-secondary-fixed` `on-secondary-fixed-variant`
## Any color
`color` takes any CSS color as is, for one that is not a role of the theme, e.g. a brand color; `ink` is then any CSS color too, white by default:
```md
npm
```
result:
`var(--brand-storybook)` `#cb3837`npm
## Poster
The two compose into material-theme-builder's [scheme poster](https://main--695eb517cb602e59b4cc045c.chromatic.com/?path=/story/mtb--custom-colors-color-match-st), of the theme of this site, in the scheme of the toggle: its grid, three accents wide and the error column, a group in each cell.
```md
```
result:
`primary` `on-primary` `primary-container` `on-primary-container`
`secondary` `on-secondary` `secondary-container` `on-secondary-container`
`tertiary` `on-tertiary` `tertiary-container` `on-tertiary-container`
`error` `on-error` `error-container` `on-error-container`
`primary-fixed` `primary-fixed-dim`
`on-primary-fixed` `on-primary-fixed-variant`
`secondary-fixed` `secondary-fixed-dim`
`on-secondary-fixed` `on-secondary-fixed-variant`
`tertiary-fixed` `tertiary-fixed-dim`
`on-tertiary-fixed` `on-tertiary-fixed-variant`
`surface-dim` `surface` `surface-bright`
`surface-container-lowest` `surface-container-low` `surface-container` `surface-container-high` `surface-container-highest`
`on-surface` `on-surface-variant` `outline` `outline-variant`
`inverse-surface` `inverse-on-surface` `inverse-primary`
`scrim` `shadow`
`note` `on-note` `note-container` `on-note-container`
`tip` `on-tip` `tip-container` `on-tip-container`
`important` `on-important` `important-container` `on-important-container`
`warning` `on-warning` `warning-container` `on-warning-container`
`caution` `on-caution` `caution-container` `on-caution-container`
]]> setCount(count + 1)}>
Count: {count}
)
}
```
```json title="docs/authoring/Example.json"
{
"initialCount": 3,
"spacing": [
{ "name": "compact", "value": 4 },
{ "name": "comfortable", "value": 12 }
]
}
```
Register them in `docs.config.ts` at your repository root:
```ts title="docs.config.ts"
import type { DocsConfig } from '@pmndrs/docs/src/config'
import { Example } from './docs/authoring/Example'
import example from './docs/authoring/Example.json'
export default {
components: { Example },
scope: { example },
} satisfies DocsConfig
```
Then use them directly in MDX:
```md
```
result:
Count: 3
Click the counter to increment it.
Server components
Components can also load data asynchronously on the server, without `'use client'`:
```tsx title="docs/authoring/Example.server.tsx"
export async function ExampleTable() {
const { default: example } = await import('./Example.json')
return (
Spacing
Pixels
{example.spacing.map(({ name, value }) => (
{name}
{value}
))}
)
}
```
Import `ExampleTable` into `docs.config.ts` and add it to `components`, then use it in MDX:
```md
```
result:
| Spacing | Pixels |
| ----------- | ------ |
| compact | 4 |
| comfortable | 12 |
## Rendering
Registered components override built-in components with the same name. Keep the configuration on the server. Add `'use client'` to interactive component modules and pass serializable props.
Website builds support CSS imports. For Tailwind classes in external components, include their files in your own Tailwind stylesheet's sources and import that stylesheet from the configuration.
HTML fragments render only the initial state, without hydration or stylesheets: the counter above displays its starting value but does not respond to clicks.
See [Configuration](/getting-started/introduction#configuration) for config paths and CLI options, and [GitHub Actions](/github-actions/introduction#custom-components-and-data) for CI setup.
]]>