# Writing content

> The MDX components you can drop into any documentation page — steps, cards, tabbed code, callouts, API fields, trees, changelogs, diagrams, and more.

Every component below is wired into the docs renderer in
`src/components/docs/mdx-components.ts` — import it at the top of any `.mdx`
file and use it inline. They are plain Astro + vanilla JS, token-resolved, and
yours to edit.

## Callouts

`<Callout>` highlights important content with a tinted border block. Four
variants cover the common intents — informational, positive, cautionary, and
destructive.

<Callout variant="note" title="Note (default)">
  An informational aside or context the reader should know.
</Callout>

<Callout variant="tip" title="Tip">
  A suggestion that makes something faster or easier.
</Callout>

<Callout variant="warning" title="Heads up">
  Something the reader should check before proceeding.
</Callout>

<Callout variant="danger" title="Warning">
  An action that could cause data loss or breakage.
</Callout>

```mdx
<Callout variant="tip" title="Custom title">
  Body copy goes in the default slot — any MDX is fine.
</Callout>
```

`variant` accepts `note` · `tip` · `warning` · `danger` (default: `note`).
`title` is optional — each variant has a built-in default.

## Steps

Use `<Steps>` with nested `<Step title="…">` for ordered walkthroughs. Numbers
and the connecting rail are drawn with a CSS counter, so reordering renumbers
automatically.

<Steps>
  <Step title="Add the import">Import `Steps` and `Step` at the top of your MDX file.</Step>
  <Step title="Wrap your actions">Each `<Step>` becomes a numbered item.</Step>
  <Step title="Ship">No client JS, no configuration.</Step>
</Steps>

```mdx
<Steps>
  <Step title="Add the import">…</Step>
  <Step title="Wrap your actions">…</Step>
</Steps>
```

## Card groups

`<CardGroup cols={2|3|4}>` lays cards out in a responsive grid that collapses to
one column on mobile. Pair it with the `Card` atom.

<CardGroup cols={2}>
  <Card>
    <CardHeader>
      <CardTitle>Tokens</CardTitle>
      <CardDescription>Edit `global.css` to reskin the whole site.</CardDescription>
    </CardHeader>
  </Card>
  <Card>
    <CardHeader>
      <CardTitle>i18n</CardTitle>
      <CardDescription>Parallel routes per locale, ready when you are.</CardDescription>
    </CardHeader>
  </Card>
</CardGroup>

## Tabbed code

`<CodeGroup>` combines several `<CodeBlock>` children into one tabbed panel.
Each child's `filename` becomes a tab — the npm/pnpm/yarn pattern, or
curl/python/node for an API.

<CodeGroup>
  <CodeBlock filename="npm" prompt>{`npm i astro-ignite`}</CodeBlock>
  <CodeBlock filename="pnpm" prompt>{`pnpm add astro-ignite`}</CodeBlock>
  <CodeBlock filename="yarn" prompt>{`yarn add astro-ignite`}</CodeBlock>
</CodeGroup>

## Highlighted code

Fenced code blocks support line highlighting and diff markers at build time
(Shiki transformers — zero client cost). Add `{1,3-5}` after the language, or
annotate lines inline.

```js {2}
const config = {
  primary: 'var(--color-primary)', // highlighted
  border: 'var(--color-border)',
};
```

```js
const before = 1; // [!code --]
const after = 2; // [!code ++]
```

## Frames

`<Frame caption="…">` centers and frames media so screenshots and diagrams look
intentional. Drop an `Image` atom or a plain `<img>` inside.

<Frame caption="A framed, captioned figure">
  <img src="/og/og-default.png" alt="Open Graph preview card" width="600" height="315" />
</Frame>

## Callouts

`<Callout variant="…">` ships six variants: `note`, `info`, `tip`, `check`,
`warning`, and `danger`.

<Callout variant="note">A neutral aside the reader can skip.</Callout>
<Callout variant="info">Background context worth knowing before you start.</Callout>
<Callout variant="tip">A shortcut or best practice.</Callout>
<Callout variant="check">Confirms a step worked — what success looks like.</Callout>
<Callout variant="warning">Setup prompts can be skipped with `--yes`.</Callout>
<Callout variant="danger">Destructive — this cannot be undone.</Callout>

## Tabs

The `Tabs` atom family is registered for MDX, so switchers work without
imports. Arrow keys move between triggers.

<Tabs defaultValue="npm">
  <TabsList>
    <TabsTrigger value="npm">npm</TabsTrigger>
    <TabsTrigger value="pnpm">pnpm</TabsTrigger>
    <TabsTrigger value="yarn">yarn</TabsTrigger>
  </TabsList>
  <TabsContent value="npm">Run `npm create astro-ignite my-site`.</TabsContent>
  <TabsContent value="pnpm">Run `pnpm create astro-ignite my-site`.</TabsContent>
  <TabsContent value="yarn">Run `yarn create astro-ignite my-site`.</TabsContent>
</Tabs>

## Accordion

`<Accordion>` + `<AccordionItem>` — native `<details name>`, so single-open
grouping needs no JS state.

<Accordion>
  <AccordionItem title="Does this template use Starlight?">
    No — the docs primitives are composed from the astro-ignite base components and owned by
    this repo.
  </AccordionItem>
  <AccordionItem title="Can I add a second locale?">
    Yes. Add it to `siteConfig.locales` and the parallel routes activate.
  </AccordionItem>
</Accordion>

## Tooltip

Wrap any inline element with `<Tooltip content="…">` to add a
<Tooltip content="Like this one."><span>hover/focus hint</span></Tooltip>.

## Expandable

`<Expandable>` is an inline disclosure for nested object shapes — distinct from
Accordion. Nest `<ParamField>` rows inside to document properties.

<Expandable title="author" hint="object · 3 properties" open>
  <ParamField name="name" type="string">
    Display name rendered in the byline and JSON-LD.
  </ParamField>
  <ParamField name="handle" type="string">Used to build the canonical author URL.</ParamField>
  <ParamField name="avatar" type="ImageMetadata">Local import, optimised at build time.</ParamField>
</Expandable>
<Expandable title="metadata" hint="object · 6 properties" />
<Expandable title="redirects" hint="Record<string, string>" />

## Fields

`<ParamField>` documents request parameters; `<ResponseField>` documents
response fields. Both take `type`, `required`, `default`, and `deprecated`;
`ParamField` adds a `location` pill (`body`, `query`, `path`, `header`).

#### Request parameters

<ParamField name="site" type="string" location="body" required>
  Target site slug. Lowercase, dashes only — matches the directory name.
</ParamField>
<ParamField name="locales" type="string[]" location="body" default="['en']">
  Locale scopes to generate. Each adds a routed subtree and sitemap block.
</ParamField>
<ParamField name="email" type="object" location="body">
  Transactional email config. Expand to see `provider` and `from`.
</ParamField>
<ParamField name="legacySlug" type="string" location="body" deprecated>
  Removed in `>=0.6`. Use the `redirects` map instead.
</ParamField>

#### Response fields

<ResponseField name="id" type="string">Unique deploy id, format `dep_xxxxxxxx`.</ResponseField>
<ResponseField name="status" type="'queued' | 'building' | 'ready'">
  Current lifecycle stage. Poll until `ready`.
</ResponseField>

## Icon

`<Icon name="…" size={…} />` renders an inline lucide-style stroke icon in
`currentColor`. Run <Icon name="terminal" size={15} /> `ignite dev` to start,
then deploy with <Icon name="rocket" size={15} /> one command.

Sizes: <Icon name="zap" size={14} /> <Icon name="zap" size={18} /> <Icon name="zap" size={24} /> <Icon name="zap" size={32} />

```mdx
Run <Icon name="terminal" size={15} /> `ignite dev` to start.
```

## Tree

`<Tree items={…}>` renders a file/folder tree. Connectors are box-drawing
glyphs — copy/paste safe, no images. Icons derive from the structure
(`folder` when an item has children, `file-code` for code extensions) and can
be overridden per item.

<Tree
  items={[
    {
      name: 'my-site/',
      children: [
        {
          name: 'src/',
          children: [
            {
              name: 'pages/',
              children: [{ name: 'index.astro' }, { name: 'blog/[slug].astro' }],
            },
            { name: 'content/', children: [{ name: 'config.ts', icon: 'file' }] },
            { name: 'lib/', icon: 'folder', muted: true, tag: 'empty' },
          ],
        },
        { name: 'astro.config.ts' },
        { name: 'plan.md', tag: 'generated' },
        { name: 'package.json' },
      ],
    },
  ]}
/>

## Update

`<Update>` renders dated changelog entries on a timeline. The rail connecting
consecutive entries draws itself — no `last` prop. Tags `minor`, `patch`, and
`breaking`/`major` are color-coded.

<Update date="May 22" version="0.5.0" tag="minor" title="Planning & budgets">
  Added `ignite plan` for previewing the route graph, and per-route bundle budgets via
  frontmatter.
</Update>
<Update date="May 04" version="0.4.2" tag="patch" title="AVIF by default">
  Image pipeline now emits AVIF with a WebP fallback. No config change needed.
</Update>
<Update date="Apr 18" version="0.4.0" tag="breaking" title="Email is explicit">
  `config.email.from` is now required. Add the field or remove the email block.
</Update>

## Mermaid

`<Mermaid code={…}>` renders a diagram from mermaid source, themed by the
design tokens. The library loads lazily — only when a diagram scrolls into
view — so pages without diagrams ship zero extra JS.

<Mermaid
  code={`graph TD
  A[src/] --> B[parse MDX]
  B --> C{budget ok?}
  C -->|yes| D[emit static]
  C -->|no| E[fail CI]`}
/>

## Columns

`<Columns cols={2|3|4}>` is the generic grid wrapper — unlike `CardGroup` it
takes any content. Columns collapse to one per row below the `md` breakpoint.

<Columns cols={3}>
  <Card>
    <CardHeader>
      <CardTitle>Fast by default</CardTitle>
      <CardDescription>Zero-JS islands, AVIF images, enforced bundle budgets.</CardDescription>
    </CardHeader>
  </Card>
  <Card>
    <CardHeader>
      <CardTitle>i18n built in</CardTitle>
      <CardDescription>Locale-scoped routes, sitemaps, and hreflang tags.</CardDescription>
    </CardHeader>
  </Card>
  <Card>
    <CardHeader>
      <CardTitle>Typed config</CardTitle>
      <CardDescription>Schema-validated at build — typos fail the build.</CardDescription>
    </CardHeader>
  </Card>
</Columns>

## Banner

`<Banner>` is a site-wide announcement strip. Drop the `solid` variant into a
layout above the header, or use `subtle` inline. Dismissal persists per `id` in
localStorage — bump the id to resurface a new announcement.

<div class="overflow-hidden rounded-md border border-border">
  <Banner id="demo-solid" href="#banner" linkText="Read the post">
    astro-ignite 0.5 is out — planning, budgets, and AVIF by default.
  </Banner>
</div>

<Banner id="demo-subtle" variant="subtle">
  Scheduled maintenance Sat 02:00–03:00 UTC. Builds may queue.
</Banner>

## Tiles

`<Tiles>` + `<Tile>` build a compact navigation grid — lighter than cards. Each
tile lifts on hover, the border darkens, and the arrow nudges right.

<Tiles cols={3}>
  <Tile icon="rocket" title="Quickstart" href="/quick-start">
    Five prompts to a deployed, audited site.
  </Tile>
  <Tile icon="terminal" title="CLI reference" href="/quick-start">
    Every flag, command, and exit code.
  </Tile>
  <Tile icon="book" title="Guides" href="/authoring">
    i18n, email, analytics, and SEO walkthroughs.
  </Tile>
  <Tile icon="package" title="Config schema" href="/quick-start">
    The typed shape of astro.config.ts.
  </Tile>
  <Tile icon="git-branch" title="Deploy API" href="/quick-start">
    Trigger and poll builds over REST.
  </Tile>
  <Tile icon="shield" title="Security" href="/quick-start">
    CSP, headers, and consent defaults.
  </Tile>
</Tiles>

<Callout variant="note" title="Add your own">
  These components live in `src/components/docs/`. Copy one as a template for a new
  component, then register it in `mdx-components.ts` to use it across every doc page.
</Callout>
