Custom React components
Define and use custom React components in MDX files with server-side rendering, Tailwind styling, and access to built-in Jamdesk components.
Define custom React components directly in MDX files. Export an arrow-function component at the top of the file and use it inline. Components render on the server at build time. They can use any Jamdesk built-in and Tailwind classes.
Basic usage
Define a component using export const at the top of your MDX file:
export const Highlight = ({ children, color }) => (
<span style={{ backgroundColor: color, padding: '0.2em 0.4em', borderRadius: '4px' }}>
{children}
</span>
);
This text has a <Highlight color="#ffeb3b">yellow highlight</Highlight> in it.
Component names must use PascalCase (start with a capital letter).
Example: Hero card
This example uses multiple props and Tailwind classes:
export const HeroCard = ({ title, description, href, icon }) => (
<a
href={href}
className="group block p-6 rounded-lg border border-gray-200 hover:border-blue-500 transition-colors"
>
<div className="flex items-center gap-3 mb-2">
<Icon icon={icon} />
<h3 className="font-semibold text-lg">{title}</h3>
</div>
<p className="text-gray-600">{description}</p>
</a>
);
<div className="grid grid-cols-2 gap-4">
<HeroCard
title="Getting started"
description="Learn the basics"
href="/quickstart"
icon="rocket"
/>
<HeroCard
title="Components"
description="Explore available components"
href="/components/overview"
icon="puzzle-piece"
/>
</div>
Using built-in components
Custom components can use any Jamdesk built-in component without an import:
export const FeatureCard = ({ title, children }) => (
<Card title={title} icon="star">
{children}
<Tip>This tip is inside a custom component!</Tip>
</Card>
);
<FeatureCard title="My Feature">
<Note>Notes work inside custom components too!</Note>
</FeatureCard>
Built-in components by category:
| Category | Components |
|---|---|
| Callouts | Note, Tip, Info, Warning, Check, Danger |
| Layout | Card, Columns, Tabs, Tab, Accordion, Steps, Step, View |
| Media | Frame, Icon, Badge, Tooltip |
| Code | CodeGroup |
| API docs | ParamField, ResponseField, Expandable |
Limitations
- Arrow functions only: Use
export constsyntax. Theexport functionsyntax is not supported. - No imports: You cannot import external packages or other files
- No hooks: React hooks (
useState,useEffect,useRef, etc.) are not available. Components are server-rendered during build. Hooks only work client-side. Use snippet files for interactive components (see troubleshooting below). - Server-side only: Components render on the server. No client-side interactivity or event handlers
- Tailwind only: Style with Tailwind CSS classes. CSS-in-JS is not available
- Strict syntax: A syntax error in an inline component fails the build with a clear error message
- PascalCase names: Component names must start with a capital letter and contain only letters and numbers (no underscores)
Need React hooks? Inline components cannot use useState, useEffect, or other hooks. They render on the server during build. Create a snippet file with the 'use client' directive for components that need state or effects (see troubleshooting section below).
Best practices
Inline components should be presentational. Request a built-in component for complex logic.
Build accessible components with proper HTML elements and ARIA attributes.
Preview your documentation locally. Check that components render correctly in dark mode too.
Troubleshooting
Error: Expected component 'MyComponent' to be defined
Causes:
- Component name doesn't use PascalCase (must start with capital letter)
- Syntax error in the component definition
- Missing closing tag or parenthesis
Solution: Check that your export follows this pattern:
export const MyComponent = ({ prop }) => (
<div>{prop}</div>
);Error: Build fails mentioning Babel or JSX syntax
Causes:
- Invalid JSX syntax in your component
- Unclosed tags or brackets
- Using unsupported JavaScript features
Solution: Verify your JSX is valid. Common issues:
- All tags must be closed (
<img loading="lazy" />not<img loading="lazy">) - Use
classNameinstead ofclass - Wrap multiple elements in a fragment
<>...</>or parent element
Warning: Inline component(s) override built-in: Note
Cause: Your inline component has the same name as a built-in component.
Solution: Rename your component to avoid conflicts:
// Instead of: export const Note = ...
export const CustomNote = ({ children }) => (
<div className="my-note">{children}</div>
);Issue: Tailwind classes aren't working
Causes:
- Using CSS-in-JS or inline style objects (limited support)
- Tailwind class not included in the build
Solution: Use standard Tailwind utility classes. Use an inline style prop with simple values for custom styles:
export const Highlight = ({ children }) => (
<span style={{ backgroundColor: '#ffeb3b' }}>{children}</span>
);Issue: Component defined with export function doesn't render
Cause: Only export const arrow function syntax is supported.
Solution: Convert your function to arrow function syntax:
// Instead of:
export function MyComponent({ prop }) {
return <div>{prop}</div>;
}
// Use:
export const MyComponent = ({ prop }) => (
<div>{prop}</div>
);Issue: Component with underscores or special characters doesn't work
Cause: Component names must be valid PascalCase identifiers (letters and numbers only).
Solution: Use only letters and numbers in your component name:
// Instead of: export const Hero_Card = ...
// Instead of: export const my-component = ...
export const HeroCard = ({ title }) => (
<div>{title}</div>
);Error: ReferenceError: useState is not defined
Cause: React hooks are not available in inline components. Inline components are server-rendered during the build. Hooks only work in client-side React components.
Solution: Create a snippet file for interactive components that need state or effects:
- Create a file in your
/snippetsdirectory (e.g.,/snippets/counter.tsx) - Add
'use client'at the top of the file - Import and use it in your MDX
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}import { Counter } from '/snippets/counter';
<Counter />