---
title: "Markdown and MDX"
description: "Write content in Markdown or MDX, use components without imports, and let the build catch broken tags."
---

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

# Markdown and MDX

Content is Markdown (`.md`) or MDX (`.mdx`). MDX lets you drop components straight into prose — cards, tabs, steps, asides, and anything you build.

## Links under a deployment base

Write site links as logical root paths without Astro's deployment base. If the site is deployed with `base: "/docs"`, write `[Installation](/installation)`, not `[Installation](/docs/installation)`. Nimbus adds `/docs` during the build.

This applies to Markdown links, reference definitions, native `<a href>` attributes, and static component `href` strings. Relative links, external URLs, fragments, query strings, code, and dynamic JSX expressions are left unchanged.

For a link computed in component or route code, import `withBase` from `@cloudflare/nimbus-docs/runtime` and apply it once:

```ts
import { withBase } from "@cloudflare/nimbus-docs/runtime";

const href = withBase("/installation", import.meta.env.BASE_URL);
```

Do not pass an already based path to `withBase`; with a `/docs` base, `withBase("/docs/installation", "/docs")` intentionally produces `/docs/docs/installation`.

## The components registry

Components listed in `src/components.ts` are available in every MDX file **without an import**:

```ts title="src/components.ts"
import { Aside } from "./components/ui/aside";
import { Card } from "./components/ui/card";
import { CardGrid } from "./components/ui/card-grid";

export const components = {
  Aside,
  Card,
  CardGrid,
};
```

```mdx
<CardGrid>
  <Card title="Fast">Built on Astro.</Card>
</CardGrid>
```

Anything not in the registry must be imported at the top of the file:

```mdx
import { FileTree } from "@/components/ui/file-tree";

<FileTree>
- src/
</FileTree>
```

## Build-time validation

A pre-build content pass checks every PascalCase tag in `src/content/**/*.mdx` against the registry plus per-file imports. Unknown components, lowercase usages, and missing imports **fail the build** instead of silently rendering as literal text on the deployed page. Opt out with `validateMdx: false` in the integration options.

## Admonitions

In `.mdx` files, Sätteri parses fenced `:::` directives and Nimbus renders them with `<Aside>`:

```mdx
:::tip
This becomes an Aside.
:::
```

### Rendered examples

These callouts are authored with directive syntax and rendered through Sätteri.

:::note
A default note with **bold text**, a [link](#admonitions), and `inline code`.
:::

:::tip[Worth knowing]
Use a title to describe the guidance in your callout.
:::

:::warning[Say "hello"]
Quoted titles stay plain strings. The `warning` alias uses the caution variant.
:::

:::danger[`Cache-Control` header]
Formatting in the title stays literal; body formatting such as **this** still works.
:::

::::note[Nested guidance]
The outer note contains a tip and a source example.

:::tip
Nested callouts use the same Aside component.
:::

```mdx
:::note
This fenced example stays code.
:::
```
::::

### Syntax and behavior

Built-in types: `note`, `info`, `tip`, `caution`, `warning`, `important`, `danger`. Add synonyms with `admonitions: { typeAliases: { heads: "tip" } }`. `Aside` must be in your components registry (the default starter exports it).

Titles use the existing plain `title` prop. Sätteri emits the string attribute;
quotes and expression-looking text are not evaluated. Markup in titles remains
literal text. Directive titles must still be valid MDX syntax because Sätteri
parses the document before the AST pass runs. For arbitrary text such as an
unclosed `<T>` or `{`, use the direct component's string prop:

```mdx
<Aside title="Use <T>">Body content.</Aside>
```

No Aside component update is required.

Sätteri parses the directives and their bodies; Nimbus maps recognized directive
nodes to Aside. Body indentation is not rewritten. Code examples, frontmatter,
expressions and JSX attributes remain protected. The existing opening-line body
form is retained for compatibility.

Nesting and fence termination follow Sätteri's native grammar. Use a longer outer
fence for nested admonitions:

```mdx
::::note
Outer content.

:::tip
Inner content.
:::
::::
```

Unknown directives remain literal by default. If your Sätteri processor explicitly
enables native directives, unrecognized nodes remain available to your own AST
plugins. Files containing standalone carriage returns
also remain literal because the installed native directive parser does not handle
those line endings consistently; LF and CRLF are supported.

This feature requires Nimbus's native Sätteri MDX pipeline. Compatible explicit
Sätteri processors are supported. For unified or another custom processor, set
`admonitions: false` and retain that processor's callout implementation. `.md`
files, raw imports and files outside `admonitions.contentDirs` are unchanged. Use
`admonitions.skip` for per-file opt-outs.

## Extending Markdown

Extend Sätteri with `markdown.mdastPlugins` for Markdown AST transformations and
`markdown.hastPlugins` for HTML AST transformations. Nimbus provides native
plugins through `@cloudflare/nimbus-docs/markdown`:

```ts title="astro.config.ts"
import { externalLinks } from "@cloudflare/nimbus-docs/markdown";

nimbus(config, {
  markdown: { hastPlugins: [externalLinks()] },
});
```

Source: https://docs.klounge.kr/writing/markdown-and-mdx/index.mdx
