Jamdesk Documentation logo

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:

CategoryComponents
CalloutsNote, Tip, Info, Warning, Check, Danger
LayoutCard, Columns, Tabs, Tab, Accordion, Steps, Step, View
MediaFrame, Icon, Badge, Tooltip
CodeCodeGroup
API docsParamField, 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. 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

1
Keep components simple

Inline components should be presentational. Request a built-in component for complex logic.

2
Use semantic HTML

Build accessible components with proper HTML elements and ARIA attributes.

3
Test thoroughly

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 className instead of class
  • 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:

  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
/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>
  );
}
your-page.mdx
import { Counter } from '/snippets/counter';

<Counter />

What's Next?

Snippets

Reusable components across pages, including interactive ones with hooks

Components Overview

Explore built-in components