Custom Components
Create your own MDX and TSX components to extend HolyDocs with custom interactive elements.
Overview
Beyond the built-in components (Callout, Card, Tabs, etc.), HolyDocs lets you create custom components that are automatically available in all your MDX pages. Place them in a _components/ directory in your docs repository and use them without imports.
HolyDocs supports two component formats:
| Format | Description | Use Case |
|---|---|---|
| MDX | Markdown-based components with prop substitution | Static layouts, reusable content blocks, styled wrappers |
| TSX | TypeScript React components rendered as hydration islands | Interactive widgets, calculators, API explorers |
Project Structure
Create a _components/ directory at the root of your docs folder (alongside docs.json):
textdocs/ docs.json introduction.mdx quickstart.mdx _components/ PricingTable.mdx ApiStatus.tsx FeatureFlag.mdx Calculator.tsx
HolyDocs auto-discovers all .mdx and .tsx files in _components/ during the build process. No registration or configuration is needed.
MDX Components
MDX components are the simplest way to create reusable content blocks. They support prop substitution and children.
Creating an MDX Component
Create a file in _components/ with the .mdx extension:
mdx{/* _components/FeatureFlag.mdx */}<Callout type="info" title={props.name}> **Status:** {props.status} {children}</Callout>
Using the Component
Reference it by filename (without extension) in any page:
mdx<FeatureFlag name="Dark Mode" status="GA"> Dark mode is now generally available on all plans.</FeatureFlag><FeatureFlag name="AI Chat" status="Beta"> AI chat is in beta. Enable it in your project settings.</FeatureFlag>
Props and Children
MDX components receive two special variables:
| Variable | Description |
|---|---|
{props.name} | Access any prop passed to the component |
{children} | Content placed between the opening and closing tags |
Props are passed as standard JSX attributes:
mdx<MyComponent title="Hello" count="3" highlighted="true"> This content is available as {children}.</MyComponent>
All prop values are strings. If you need boolean or numeric logic, handle the string comparison in your component template (e.g., check for {props.highlighted} equals "true").
MDX Component Example: API Endpoint
A reusable API endpoint reference card:
mdx{/* _components/Endpoint.mdx */}<div className="endpoint-card"> <div className="endpoint-method">{props.method}</div> <code>{props.path}</code> {children} <Expandable title="Authentication"> This endpoint requires a Bearer token. See [Authentication](/api/authentication). </Expandable></div>
Usage:
mdx<Endpoint method="GET" path="/api/v1/users"> Returns a paginated list of users in your organization.</Endpoint><Endpoint method="POST" path="/api/v1/users"> Creates a new user. Requires `name` and `email` in the request body.</Endpoint>
TSX Components
TSX components are full React components that run as hydration islands on the client side. Use them for interactive elements that need JavaScript.
Creating a TSX Component
tsx// _components/Calculator.tsxinterface CalculatorProps { defaultValue?: string;}export default function Calculator({ defaultValue = "0" }: CalculatorProps) { const [value, setValue] = React.useState(defaultValue); const [result, setResult] = React.useState(""); function calculate() { try { setResult(String(eval(value))); } catch { setResult("Error"); } } return ( <div className="p-4 border rounded-lg"> <input type="text" value={value} onChange={(e) => setValue(e.target.value)} className="w-full p-2 border rounded mb-2" /> <button onClick={calculate} className="px-4 py-2 bg-primary text-white rounded"> Calculate </button> {result && <div className="mt-2 text-lg font-mono">{result}</div>} </div> );}
Using TSX Components
mdxTry the interactive calculator:<Calculator defaultValue="2 + 2" />
Hydration Islands
TSX components render as hydration islands: the HTML is server-rendered during the build, then the component hydrates on the client for interactivity. This means:
- The component's initial HTML is visible immediately (no layout shift)
- JavaScript loads asynchronously and attaches event handlers
- Components are isolated from each other and from the page
TSX components run in a sandboxed environment. The following are restricted for security:
- Node.js built-in modules (
fs,path,child_process, etc.) - Global network APIs (
fetch,XMLHttpRequest,WebSocket) - Dangerous globals (
eval,Function,process,require)
If your component needs external data, pass it as props from your MDX page or use template variables.
Component Categories
When viewing components in the dashboard (Components page), they are organized by category:
| Category | Description |
|---|---|
layout | Structural components (grids, containers, wrappers) |
display | Content display (badges, stats, feature cards) |
input | Interactive inputs (toggles, selectors, calculators) |
navigation | Navigation aids (breadcrumbs, page links) |
feedback | User feedback elements (rating widgets, polls) |
custom | Default category for uncategorized components |
HolyDocs infers the category from the component's usage patterns, or you can specify it with a frontmatter comment:
mdx{/* _components/PricingTable.mdx */}{/* @category display */}| Plan | Price | Features ||---|---|---|| Free | $0 | {props.freeFeatures} || Pro | {props.proPrice} | {props.proFeatures} |
Using Built-in Components Inside Custom Components
Custom MDX components can nest any built-in HolyDocs component:
mdx{/* _components/QuickLinks.mdx */}<CardGroup cols={2}> <Card title={props.title1} icon={props.icon1} href={props.href1}> {props.desc1} </Card> <Card title={props.title2} icon={props.icon2} href={props.href2}> {props.desc2} </Card></CardGroup>
Template Variables in Components
Custom components can use template variables just like regular pages:
mdx{/* _components/ApiQuickstart.mdx */}Install the {{sdk}} SDK:<CodeGroup> ```bash npm npm install @{{org_name}}/sdk ``` ```bash pip pip install {{org_name}}-sdk ```</CodeGroup>Then initialize the client with your API key:```const client = new Client("{{api_key}}");```
Template variables are resolved before custom components are expanded, so {{variable}} syntax works identically in components and pages.
Viewing and Managing Components
The dashboard Components page shows all custom components in your project:
- Component name, format (MDX/TSX), and category
- Usage count (how many pages reference the component)
- Props with types and descriptions
- Live preview with example usage
Best Practices
If a component doesn't need client-side interactivity, use MDX. It's simpler, faster, and doesn't add JavaScript to the page.
Each TSX component is a separate hydration island. Large components increase bundle size. Break complex UIs into smaller, composable pieces.
Use descriptive prop names like apiEndpoint instead of url. Future maintainers (and the dashboard UI) benefit from self-documenting props.
Run holydocs dev to preview custom components in your local development server. Changes to _components/ files trigger hot reload.