Skip to main content

React Component Support

Mintlify MDX supports embedding custom React components directly in your documentation:
  • Import components from the snippets folder
  • Use React hooks (useState, useEffect, etc.)
  • Create interactive demos, calculators, and visualizations
  • Build reusable component libraries
snippets/ColorPicker.jsx
export const ColorPicker = () => {
  const [color, setColor] = React.useState('#3CD0E2');

  return (
    <div>
      <input
        type="color"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
      <p>Selected: {color}</p>
    </div>
  );
};
Using the Component
import { ColorPicker } from '/snippets/ColorPicker';

## Try It Out

<ColorPicker />
React components must be placed in the snippets folder and exported as named exports. You cannot import components from arbitrary MDX files.

Preventing the Double Flash

Mintlify pages render server-side first, then hydrate on the client. React components that modify layout or inject dynamic content cause a double flash: the server-rendered markup appears, disappears during hydration, then re-renders with client state. This creates a jarring flicker. The fix: render your component invisible on mount, then fade it in after hydration completes.
Every client-side React component should follow this three-part pattern:
  1. State gate: useState(false) tracks whether the component has mounted
  2. Mount trigger: useEffect(() => set(true), []) fires once after hydration
  3. Opacity transition: the wrapper starts at opacity: 0 and transitions to 1
snippets/MyComponent.jsx
export const MyComponent = () => {
  const [ready, setReady] = React.useState(false);

  React.useEffect(() => {
    setReady(true);
  }, []);

  return (
    <div style={{
      opacity: ready ? 1 : 0,
      transition: 'opacity 0.5s ease-out'
    }}>
      {/* your content */}
    </div>
  );
};
Never use display: none or visibility: hidden for this purpose. Those properties trigger layout reflows when they change, causing the content to “jump” into place. The opacity + transition approach is GPU-accelerated and reflow-free.
Rules of thumb:
  • Only opacity and transform are safe for mount animations — they don’t trigger reflows
  • Always use ease-out timing — it feels responsive because the fast phase happens first
  • Keep transitions under 0.8s — longer feels sluggish, shorter can still flash
  • Use translateZ(0) to force GPU compositing, even if you’re not animating position

Loading External Scripts

Mintlify doesn’t support <script> tags directly in MDX. Instead, use Mintlify’s built-in load() function to fetch external libraries at runtime. Chain multiple load() calls with .then() to guarantee dependency order, and guard your initialization against Mintlify’s hydration timing.
snippets/AnimatedSection.jsx
export const AnimatedSection = ({ children }) => {
  const [ready, setReady] = React.useState(false);

  React.useEffect(() => {
    load('https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js')
      .then(function () {
        return load('https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/ScrollTrigger.min.js');
      })
      .then(function () {
        // Wait for DOM to be fully ready
        if (document.readyState === 'complete') {
          initAnimations();
        } else {
          window.addEventListener('load', initAnimations);
        }
        // Also try after a delay in case Mintlify hydration is still running
        setTimeout(function () {
          if (ScrollTrigger.getAll().length === 0) {
            initAnimations();
          }
        }, 1500);
      })
      .catch(function (e) {
        console.error('GSAP load failed:', e);
      });

    setReady(true);
  }, []);

  function initAnimations() {
    gsap.registerPlugin(ScrollTrigger);
    // Your GSAP animations here
  }

  return (
    <div style={{
      opacity: ready ? 1 : 0,
      transition: 'opacity 0.5s ease-out'
    }}>
      {children}
    </div>
  );
};
The pattern has three key parts:
  1. Sequential loading: Chain .then() calls so dependent libraries (like ScrollTrigger) load after their prerequisites (GSAP core)
  2. Hydration-safe init: Check document.readyState first, then also set a setTimeout fallback — Mintlify’s hydration can delay DOM readiness beyond the browser’s load event
  3. Idempotency guard: Before the timeout fires, check whether initialization already ran (e.g., ScrollTrigger.getAll().length === 0) to avoid double-initializing
The load() function is a Mintlify global — it is not a standard browser API and won’t work outside of Mintlify. Don’t confuse it with dynamic import() or other module loaders.
Pin your CDN URLs to specific versions (e.g., gsap@3.14.1) rather than using @latest. Unpinned versions can break your docs without warning when the library ships a breaking change.