---
title: "Writing pages and components"
description: "Create pages, organise navigation, and write documentation with Markdown and MDX components."
---

> Documentation Index
> Fetch the complete documentation index at: https://petwan.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing pages and components

Every page is an `.mdx` file in `src/content/docs/`. The file path becomes the URL, so `src/content/docs/guides/deploy.mdx` is served at `/guides/deploy/`.

## 1. Create a page

Add a file with a `title` in its frontmatter. The title becomes the page heading, so don't repeat it in the body.

```mdx title="src/content/docs/guides/deploy.mdx" {2-3}
---
title: Deploy
description: Ship the site to production.
---

Run the build, then upload the output.
```

Frontmatter is validated at build time. A missing title or a misspelled field fails the build with a message that names the file and the field.

- `title` is required. Everything else is optional.
- `description` appears under the heading, in search results, and in social cards.
- `sidebar` controls where the page sits in the navigation.
  - `order` sorts pages within a group, lowest first.
  - `badge` adds a short label next to the link.

### 1.1 Frontmatter fields

| Field | Type | Description |
| --- | --- | --- |
| `title` | string | The page heading and the `<title>` tag. |
| `description` | string | One-line summary for search, social cards, and the page header. |
| `sidebar.order` | number | Position within the sidebar group. Lower numbers come first. |
| `draft` | boolean | Builds the page in development only. It never reaches production. |

## 2. Write with Markdown

Standard Markdown works everywhere: **bold**, *italic*, [links](/), `inline code`, lists, tables, and fenced code blocks. Press <kbd>⌘</kbd> <kbd>K</kbd> to search the site for anything else.

> Write the page you would want to find. One topic per page, and a title that says what the reader will be able to do.

Code blocks are highlighted at build time. Add a `title` to show a filename, and line numbers in braces to draw attention to specific lines.

```ts title="astro.config.ts" {3}
const nimbusConfig = defineNimbusConfig({
  site: "https://docs.example.com",
  title: "My docs",
  description: "Guides and reference for My product.",
});
```

> **Preview as you write**
>
> The dev server reloads the page on every save. Run `npm run dev` and keep the page open next to your editor.

### 2.1 Organise the sidebar

Folders become sidebar groups. Inside a group, pages sort by `sidebar.order`, then by title.

1. Create a folder in `src/content/docs/`, for example `guides/`.
2. Move related pages into it.
3. Set `sidebar.order` on each page to fix the order.

## 3. Use components

Components are available in every `.mdx` file without an import. Install more from the registry when you need them.

1. **Add a component**

   Run `npx @cloudflare/nimbus-docs add accordion` to copy it into `src/components/ui/`.
2. **Register it**

   Add it to `src/components.ts` so every page can use it.

### npm

```sh
npx @cloudflare/nimbus-docs add accordion
```

### pnpm

```sh
pnpm dlx @cloudflare/nimbus-docs add accordion
```

### 3.1 What ships with the starter

<dl>
  <dt>Aside</dt>
  <dd>Notes, tips, cautions, and warnings that stand apart from the text.</dd>
  <dt>Steps</dt>
  <dd>Numbered procedures, each with a title and its own content.</dd>
  <dt>Tabs</dt>
  <dd>The same content in several variants, such as one command per package manager.</dd>
</dl>

<details>
  <summary>Can I use plain Markdown files instead of MDX?</summary>

  Yes. MDX is a superset of Markdown, so any `.md` content works unchanged in an `.mdx` file. You only need MDX syntax when you add a component.
</details>

---

> **Note**
>
> Every page also has a Markdown version for agents and readers who prefer plain text. Add `index.md` to any page URL to see it.

Pages you write today keep working when you upgrade Nimbus[^1]. Use <mark>highlights</mark> sparingly; they lose their meaning when everything is marked.

[^1]: Changes that need your attention are listed by `npx @cloudflare/nimbus-docs migrate --dry-run`.

## 4. Component reference

Every `.mdx` file can use these components without an import. Each one is a file in `src/components/ui/`, so you can change how it looks or works.

### 4.1 Aside

Set a note, tip, caution, or warning apart from the text around it.

> **Note**
>
> Pages are validated at build time.

> **Before you deploy**
>
> Set `site` in `astro.config.ts` to your production URL.

```mdx
<Aside type="note">
  Pages are validated at build time.
</Aside>

<Aside type="caution" title="Before you deploy">
  Set `site` in `astro.config.ts` to your production URL.
</Aside>
```

| Prop | Value |
|---|---|
| `type` | `note` (default), `tip`, `caution`, or `danger` |
| `title` | Text above the content. Defaults to the type's name. |
| `icon` | Any `ph:` icon name, in place of the type's icon. |

For a title with formatting, pass it as a slot: `<Fragment slot="title">Uses <code>env</code></Fragment>`.

### 4.2 Steps

Number a procedure. Each step has a title and its own content.

1. **Create the page**

   Add `src/content/docs/deploy.mdx` with a `title`.
2. **Preview it**

   Run `npm run dev` and open the page.

```mdx
<Steps>
  <Step title="Create the page">
    Add `src/content/docs/deploy.mdx` with a `title`.
  </Step>
  <Step title="Preview it">
    Run `npm run dev` and open the page.
  </Step>
</Steps>
```

| Prop | Value |
|---|---|
| `start` on `Steps` | The first step's number. Defaults to 1. |
| `title` on `Step` | The step's heading. Also accepts a `title` slot. |

### 4.3 Tabs

Show the same content in several variants. Give tabs a `syncKey`, and the reader's choice carries over to every set of tabs with the same key.

### macOS

Press <kbd>⌘</kbd> <kbd>K</kbd> to search.

### Windows

Press <kbd>Ctrl</kbd> <kbd>K</kbd> to search.

```mdx
<Tabs syncKey="os">
  <TabItem label="macOS">
    Press <kbd>⌘</kbd> <kbd>K</kbd> to search.
  </TabItem>
  <TabItem label="Windows">
    Press <kbd>Ctrl</kbd> <kbd>K</kbd> to search.
  </TabItem>
</Tabs>
```

| Prop | Value |
|---|---|
| `syncKey` on `Tabs` | Tabs with the same key switch together, and remember the choice. |
| `label` on `TabItem` | The tab's name. Required. |

### 4.4 Package managers

Write an install command once and show it for npm, pnpm, Yarn, and Bun.

```sh
npm i your-package
yarn add your-package
pnpm add your-package
bun add your-package
```

```mdx
<PackageManagers pkg="your-package" />
```

Add `dev` for a dev dependency, or `type="run" args="build"` for a script.

### 4.5 Cards

Link readers to the next places to go, in a grid that stacks on small screens.

- **Write a page** — Create an `.mdx` file in `src/content/docs/`.
- **Change the theme** — Edit the colour and type tokens in `src/styles/globals.css`.

```mdx
<CardGrid>
  <Card title="Write a page" icon="ph:pencil-simple">
    Create an `.mdx` file in `src/content/docs/`.
  </Card>
  <Card title="Change the theme" icon="ph:palette">
    Edit the colour and type tokens in `src/styles/globals.css`.
  </Card>
</CardGrid>
```

| Prop | Value |
|---|---|
| `title` on `Card` | The card's heading. Also accepts a `title` slot. |
| `icon` on `Card` | Any `ph:` icon name. Also accepts an `icon` slot. |

Icons come from [Phosphor](https://phosphoricons.com): use any name with the `ph:` prefix.

### 4.6 Partials

Write a passage once and include it on any page. Partials live in `src/content/partials/`.

This text lives in `src/content/partials/example.mdx` and is pulled in with
`<Render file="example" />`. Edit the partial once and every page that renders
it stays in sync — handy for install steps, support notes, or any boilerplate
you'd otherwise copy between pages.

```mdx
<Render file="example" />
```

### 4.7 Restyle any part

Every part of every component has a `data-slot` attribute, so you can restyle one piece from `src/styles/globals.css` without editing the component.

```css title="src/styles/globals.css"
[data-slot="card-title"] {
  letter-spacing: -0.01em;
}
```

Corners come from two tokens in the same file: `--nb-radius` for cards, callouts, code blocks and buttons, and `--nb-radius-sm` for the small marks inside them. Change them to re-round the whole site.

### 4.8 Add more

Accordions, file trees, link cards, embeds, and more install from the registry when you need them. Each one lands as a file in `src/components/ui/`, like the components above.

```sh
npx @cloudflare/nimbus-docs add accordion
yarn dlx @cloudflare/nimbus-docs add accordion
pnpm dlx @cloudflare/nimbus-docs add accordion
bunx @cloudflare/nimbus-docs add accordion
```

## 5. FileTree

Use a nested Markdown list to describe a directory structure. End directory names with `/`, highlight important files with `**name**`, and use `...` for omitted entries.

- src/
  - content/
    - docs/
      - **writing-pages.mdx**
  - components/
    - ui/
- astro.config.ts
- package.json

```mdx
<FileTree>

- src/
  - content/
    - docs/
      - **writing-pages.mdx**
  - components/
    - ui/
- astro.config.ts
- package.json

</FileTree>
```

## 6. Accordion

Use collapsible sections for optional explanations. Set open on an Accordion to start expanded.

Do components need imports?

Components registered in src/components.ts are available in every MDX article.

Where should shared text live?

Use a partial and the Render component.

```mdx
<AccordionGroup>
  <Accordion>
    <AccordionTrigger>Do components need imports?</AccordionTrigger>
    <AccordionContent>Components registered in src/components.ts are available in every MDX article.</AccordionContent>
  </Accordion>
  <Accordion>
    <AccordionTrigger>Where should shared text live?</AccordionTrigger>
    <AccordionContent>Use a partial and the Render component.</AccordionContent>
  </Accordion>
</AccordionGroup>
```

## 7. CodeGroup

Use titles to label code alternatives. Each fenced block becomes one panel.

```js
const greeting = "Hello, Petwan";
```

```ts
const greeting: string = "Hello, Petwan";
```

````mdx
<CodeGroup titles={["JavaScript", "TypeScript"]}>

```js
const greeting = "Hello, Petwan";
```

```ts
const greeting: string = "Hello, Petwan";
```

</CodeGroup>
````

## 8. CopyPrompt

The text prop supplies the full prompt copied to the clipboard, even if the displayed line is truncated.

```mdx
<CopyPrompt text="Read the Writing pages and components guide, then help me draft a clear MDX article using the installed components." />
```

## 9. Embed

Provide src and an accessible title; ratio defaults to 16/9. This local demo avoids depending on an external provider. External sites must permit embedding.

```mdx
<Embed src="/embed-demo.html" title="Petwan embedded content example" ratio="16/9" />
```

## 10. Frame

Wrap a screenshot or demo with a caption. Imported images passed through src use Astro image optimization; public image paths use a plain image.

<img src="/logo.svg" alt="Petwan letter P logo" width="128" height="128" />

```mdx
<Frame caption="The Petwan P mark">
  <img src="/logo.svg" alt="Petwan letter P logo" width="128" height="128" />
</Frame>
```

## 11. LinkCard

Use href, title and an optional description. The entire card is a link.

- [Petwan Journal](/) — Return to the homepage and browse the latest notes.

```mdx
<LinkCard href="/" title="Petwan Journal" description="Return to the homepage and browse the latest notes." />
```

## 12. Popover

Compose the root, trigger and content. Keep PopoverTrigger a direct child of Popover. Supported placements are bottom-start, bottom-end, top-start and top-end.

Writing tip

<p>Use one clear topic per article and runnable examples.</p>

```mdx
<Popover>
  <PopoverTrigger class="rounded-md border border-border px-3 py-2 text-sm">Writing tip</PopoverTrigger>
  <PopoverContent placement="bottom-start">
    <p>Use one clear topic per article and runnable examples.</p>
  </PopoverContent>
</Popover>
```

## 13. Lightbox

Give a figure a caption and a click-to-zoom surface. Plain Markdown images already zoom, so wrap only when you want an explicit caption.

```mdx
<Lightbox src="/logo.svg" alt="Petwan letter P logo" caption="The Petwan P mark" />
```

| Prop | Value |
|---|---|
| `src` | Image path. Required. |
| `alt` | Accessible description. Required. |
| `caption` | Optional text below the image. |
| `class` | Extra class on the `img` element. |

## 14. Math formulas

MathFormula is not registered globally — import it explicitly. The `tex` prop is a string expression, so backslashes must be escaped as `\\`.

```mdx
import MathFormula from "../../components/MathFormula.astro";

<MathFormula tex={"E = mc^2"} display />
```

A bare `$$ … $$` block does not render here: MDX reads the braces as a JSX expression and the build fails. Use the component.

Source: https://petwan.tech/writing-pages/index.mdx
