MDX Authoring Patterns
Practical reference for writing documentation in this repo. This site uses Astro + Starlight with MDX.
Auto-Imported Components
<Tabs> and <Tab> are globally available — no import statement needed. Tabs with matching labels auto-sync across the page.
```python
from strands import Agent
agent = Agent(tools=[my_tool])
```
```typescript
--8<-- "user-guide/concepts/agents/example.ts:basic_agent"
```<Syntax> is also globally available. Use it when shared prose needs to reference a language-specific identifier — any method name, parameter, class name, or short expression that differs between Python and TypeScript:
Use <Syntax py=".as_tool()" ts=".asTool()" /> to customize the tool name.
The <Syntax py="Agent.retry_strategy" ts="Agent.retryStrategy" /> parameter controls retry behavior.Renders as inline <code> by default. Pass plain for plain text. Reacts live to the global language toggle — no page reload.
When to use <Syntax> vs <Tabs>:
<Syntax>: single identifier or short expression that differs by language, embedded in a prose sentence.<Tabs>: code blocks, multi-line examples, or content that's structurally different between languages.
Never spell out both language variants manually in prose. Use <Syntax> instead.
For other Starlight components (Aside, Card, CardGrid, LinkCard, Icon, Badge), use explicit imports:
import { Card, CardGrid } from '@astrojs/starlight/components';Snippet Inclusion
Code lives in runnable source files; the MDX page references named regions of those files. Snippet source files live alongside the page that includes them, with the same base name (agent-loop.mdx pulls from agent-loop.ts). Imports go in a sibling *_imports.ts file; the body lives in the main .ts file. The MDX block pulls both — imports first, then body — so a single rendered code block shows imports + usage.
Snippet names use snake_case (build-time identifiers, not source code) and follow <feature>_<variant> for bodies, plus _imports suffix for the matching import set.
1. Imports file — one snippet per import set, named <name>_imports. Add // @ts-nocheck at the top only when the same identifier is intentionally re-imported across multiple snippets in this file:
// traces_imports.ts
// --8<-- [start:code_configuration_option1_imports]
import { Agent } from '@strands-agents/sdk'
// --8<-- [end:code_configuration_option1_imports]2. Body file — runnable code, body snippets wrapped in functions for scoping (see "TypeScript Snippet Scoping" below):
// traces.ts
async function codeConfigurationOption1() {
// --8<-- [start:code_configuration_option1]
const agent = new Agent({
systemPrompt: 'You are a helpful AI assistant',
})
// --8<-- [end:code_configuration_option1]
}3. MDX page — pull both into one code block. Both --8<-- directives go inside a single ```typescript fence, separated by a blank line. The reader sees imports above the body in one continuous, copy-pasteable code box:
```typescript
--8<-- "user-guide/observability-evaluation/traces_imports.ts:code_configuration_option1_imports"
--8<-- "user-guide/observability-evaluation/traces.ts:code_configuration_option1"
```Common mistake — do not wrap each include in its own fence. Two fences render as two disconnected code blocks with a visible gap, which breaks the copy-paste-as-one-unit affordance:
```typescript
--8<-- "user-guide/observability-evaluation/traces_imports.ts:code_configuration_option1_imports"
```
```typescript
--8<-- "user-guide/observability-evaluation/traces.ts:code_configuration_option1"
```Only the code between markers is rendered. Paths inside the --8<-- directive are always resolved relative to site/src/content/docs/.
When to skip the imports file: the page renders no TypeScript snippets, or every snippet body genuinely has zero external imports. Otherwise, write the imports file — relying on the imports at the top of the body .ts is wrong, since those lines live above the [start:...] marker and never appear in rendered docs.
Callout Syntax
Starlight-native admonitions. Use sparingly — callouts are visually loud and reset the reader's flow. In most cases inline prose is the better choice; reach for a callout only when the information genuinely needs to break frame. Overusing caution/danger in particular trains readers to ignore them.
When you do use one, pick the level that matches consequence:
:::note— context the reader needs but might miss; non-actionable. Use for: clarifications, links to related concepts.:::tip— an optional improvement or shortcut. Use for: performance hints, idiomatic alternatives, "you can also…" suggestions.:::caution— something that will work but has a non-obvious gotcha or cost. Strongest when the failure is silent or hard to diagnose (wrong-but-not-broken output, a config that degrades behavior without erroring) and the reader could plausibly hit it on the happy path.:::danger— even rarer. Only for actions that destroy work, lose data, or have no recovery path.
:::note[Optional Title]
Informational content.
:::
:::tip
Helpful suggestion.
:::
:::caution
Proceed carefully.
:::
:::danger[Breaking Change]
This will break existing code.
:::Frontmatter Schema
Required fields:
---
title: "Page Title"
description: "Short description for SEO (140-160 chars)"
---Optional fields (validated by Zod in site/src/content.config.ts):
| Field | Type | Purpose |
|---|---|---|
languages | string | string[] | Feature only available in specific SDK language(s) |
community | boolean | Marks page as community-contributed |
experimental | boolean | Marks feature as experimental |
integrationType | enum | model-provider, tool, session-manager, integration, plugin, agent-extension |
category | string | For TypeScript API doc grouping |
redirectFrom | string[] | Old slugs that should redirect here |
tags | Tag[] | From site/src/config/tags.yml; drives the build-time "Related pages" block |
sourceLinks | \{repo, path\}[] | Pointers to SDK implementation; rendered on headless surfaces (index.md, llms-full.txt) |
These render contextual banners automatically (experimental → community → languages). Anything not in this table is silently stripped by Zod at build time, so don't invent fields like contentType or lastReviewed — add them to the schema first if they'd be useful.
Do not set languages: [python, typescript] — listing all supported languages is redundant. Omit the field when a feature is available in all languages.
TypeScript Snippet Scoping
When a .ts file has multiple snippets using the same variable names, wrap each snippet body in a function. Place markers inside the function so only the snippet body — not the function declaration — appears in docs:
// Correct: function is for scoping only
async function exampleScope() {
// --8<-- [start:example]
const result = await agent.invoke('Hello')
console.log(result)
// --8<-- [end:example]
}TypeScript uses isolatedModules: true — multiple top-level snippets that redeclare the same identifiers will fail type-checking without scoping. Body files generally should not need // @ts-nocheck; if they do, the snippets aren't scoped correctly.
Markers must be on their own line as line comments — anything else on the line and the build won't detect them. A typo in the snippet name fails to render the block; verify each --8<-- reference resolves to a real [start:…]/[end:…] pair.
Relative Links
Use relative file paths. They resolve automatically via the PageLink component:
[Custom tools](../tools/custom-tools.md)
[Tools overview](../tools/index.md)API Reference Shorthand
Link to generated API docs without fragile relative paths:
[@api/python/strands.agent.agent](AgentResult)
[@api/typescript/Agent#constructor](Agent constructor)Line Length
90 characters maximum for files under site/src/content/docs/. Template literal contents in snippet files must also stay under 90 characters. Prettier does not enforce this automatically.
Validating Changes
Run from site/:
npm run typecheck:snippetstype-checks snippet files undersrc/content/docs/npm run typechecktype-checks other TypeScriptnpm run formatapplies Prettier formatting to doc snippet filesnpm testruns the site's unit testsnpm run devpreviews at http://localhost:4321/ to confirm snippets render
Gotchas
- Snippet dedent: Leading whitespace is automatically stripped from included snippets. If the snippet reference in markdown is indented, content indents to that level.
- Tab indentation: Content inside
<Tab>must not have leading blank lines or it may not render correctly. - API docs are symlinked:
site/src/content/docs/api/python/_generatedandtypescript/_generatedare symlinks tosite/.build/api-docs/. Never edit these directly — they are auto-generated. - No
TabItem: The auto-imported component isTab, notTabItem(even though Starlight's native component is called TabItem). - Code theme: Uses GitHub Light/Dark themes. Follows Starlight's
[data-theme]attribute. - Formatting: No semicolons, single quotes, 2-space indent, trailing commas ES5 style (enforced by Prettier).