---
title: Custom React components
description: You can define custom React components directly in your MDX files, giving you full control over the presentation of your content.
---

Define custom React components directly in MDX files. Export an arrow-function component at the top of the file and use it inline. Components are server-rendered at build time with access to all Jamdesk built-ins and Tailwind classes.

## Basic usage

Define a component using `export const` at the top of your MDX file:

```mdx
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.
```

<Note>
Component names must use PascalCase (start with a capital letter).
</Note>

## Example: Hero card

Here's a more complex example with multiple props and Tailwind classes:

```mdx
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

Your custom components have access to all of Jamdesk's built-in components. No imports needed:

```mdx
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 const` syntax. The `export function` syntax 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, and hooks only work client-side. For interactive components, use snippet files instead (see troubleshooting below).
- **Server-side only**: Components render on the server; no client-side interactivity or event handlers
- **Tailwind only**: Use Tailwind CSS classes for styling; CSS-in-JS is not available
- **Strict syntax**: Syntax errors in inline components will fail 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)

<Warning>
**Need React hooks?** Inline components cannot use `useState`, `useEffect`, or other hooks because they're rendered on the server during build. For interactive components that need state or effects, create a snippet file with the `'use client'` directive (see troubleshooting section below).
</Warning>

## Best practices

<Steps>
<Step title="Keep components simple">
Inline components should be presentational. For complex logic, request a built-in component.
</Step>
<Step title="Use semantic HTML">
Ensure your components are accessible with proper HTML elements and ARIA attributes.
</Step>
<Step title="Test thoroughly">
Preview your documentation locally to verify components render correctly in both light and dark modes.
</Step>
</Steps>

## Troubleshooting

<Accordion title="Component not found error">
**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:
```mdx
export const MyComponent = ({ prop }) => (
  <div>{prop}</div>
);
```
</Accordion>

<Accordion title="Build fails with syntax error">
**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 />` not `<img>`)
- Use `className` instead of `class`
- Wrap multiple elements in a fragment `<>...</>` or parent element
</Accordion>

<Accordion title="Component overrides built-in warning">
**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:
```mdx
// Instead of: export const Note = ...
export const CustomNote = ({ children }) => (
  <div className="my-note">{children}</div>
);
```
</Accordion>

<Accordion title="Styles not applying">
**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. For custom styles, use inline `style` prop with simple values:
```mdx
export const Highlight = ({ children }) => (
  <span style={{ backgroundColor: '#ffeb3b' }}>{children}</span>
);
```
</Accordion>

<Accordion title="Component not rendering (export function)">
**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:
```mdx
// Instead of:
export function MyComponent({ prop }) {
  return <div>{prop}</div>;
}

// Use:
export const MyComponent = ({ prop }) => (
  <div>{prop}</div>
);
```
</Accordion>

<Accordion title="Component name has invalid characters">
**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:
```mdx
// Instead of: export const Hero_Card = ...
// Instead of: export const my-component = ...
export const HeroCard = ({ title }) => (
  <div>{title}</div>
);
```
</Accordion>

<Accordion title="useState is not defined (or useEffect, useRef, etc.)">
**Error:** `ReferenceError: useState is not defined`

**Cause:** React hooks are not available in inline components. Inline components are server-rendered during the build process, and hooks only work in client-side React components.

**Solution:** For interactive components that need state or effects, create a snippet file instead:

1. Create a file in your `/snippets` directory (e.g., `/snippets/counter.tsx`)
2. Add `'use client'` at the top of the file
3. Import and use it in your MDX

```tsx title="/snippets/counter.tsx"
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}
```

```mdx title="your-page.mdx"
import { Counter } from '/snippets/counter';

<Counter />
```
</Accordion>

## What's Next?

<Columns cols={2}>
  <Card title="Snippets" icon="scissors" href="/content/snippets">
    Reusable components across pages, including interactive ones with hooks
  </Card>
  <Card title="Components Overview" icon="puzzle-piece" href="/components/overview">
    Explore built-in components
  </Card>
</Columns>
