MUI Docs Infra

Code Controller Context

The CodeControllerContext provides a React context for managing controlled code state in interactive code editing and demo scenarios. It enables real-time code editing with syntax highlighting and live component previews.

Features

  • Interactive code editing - Edit source code with real-time updates
  • Live component previews - See component changes rendered instantly
  • Context-based state - Shared state across multiple components
  • Simple integration - Works with existing CodeHighlighter and demo components

Examples

Live Code Editor

live-example.js
// Welcome to the live code editor!
function greet(name) {
  return `Hello, ${name}!`;
}
import * as React from 'react';
import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
import { createParseSource } from '@mui/internal-docs-infra/pipeline/parseSource';

import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider';
import { CodeController } from './CodeController';
import { CodeEditorContent } from './CodeEditorContent';

const initialCode = {
  Default: {
    url: 'file://live-example.js',
    fileName: 'live-example.js',
    source: `// Welcome to the live code editor!
function greet(name) {
  return \`Hello, \${name}!\`;
}
`,
  },
};

export function CodeEditor() {
  return (
    <CodeProvider>
      <CodeController>
        <CodeHighlighter
          url={initialCode.Default.url}
          Content={CodeEditorContent}
          code={initialCode}
          controlled
          sourceParser={createParseSource()}
        />
      </CodeController>
    </CodeProvider>
  );
}

Live Demo

Type Whatever You Want Below

CheckboxBasic.tsx
import * as React from 'react';
import { Checkbox } from '@/components/Checkbox';

export default function CheckboxBasic() {
  return (
    <div>
      <Checkbox defaultChecked />
      <p style={{ color: '#CA244D' }}>Type Whatever You Want Below</p>
    </div>
  );
}
import * as React from 'react';
import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider';
import { DemoController } from './DemoController';
import { DemoCheckboxBasic } from './demo-basic';

export function DemoLive() {
  return (
    <CodeProvider>
      <DemoController>
        <DemoCheckboxBasic />
      </DemoController>
    </CodeProvider>
  );
}

Multi-File Editor

import React from 'react';

export default function App() {
  return (
    <div className="container">
      <h1>Multi-File Demo</h1>
      <p>Edit both the component and CSS!</p>
    </div>
  );
}
import * as React from 'react';
import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
import { createParseSource } from '@mui/internal-docs-infra/pipeline/parseSource';
import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider';
import { CodeController } from './CodeController';
import { MultiFileContent } from './MultiFileContent';

const initialCode = {
  Default: {
    url: 'file:///App.tsx',
    fileName: 'App.tsx',
    source: `import React from 'react';

export default function App() {
  return (
    <div className="container">
      <h1>Multi-File Demo</h1>
      <p>Edit both the component and CSS!</p>
    </div>
  );
}`,
    extraFiles: {
      'styles.css': {
        source: `.container {
  padding: 20px;
  background: #f5f5f5;
  border-radius: 8px;
}

h1 {
  color: #333;
  margin-bottom: 10px;
}

p {
  color: #666;
  font-size: 14px;
}`,
      },
    },
  },
};

export function MultiFileEditor() {
  return (
    <CodeProvider>
      <CodeController>
        <CodeHighlighter
          url={initialCode.Default.url}
          Content={MultiFileContent}
          code={initialCode}
          controlled
          sourceParser={createParseSource()}
        />
      </CodeController>
    </CodeProvider>
  );
}

Basic Usage

The Code Controller provides a simple React context for managing controlled code state. It works with two main patterns:

Pattern 1: Code Editor

For interactive code editing with syntax highlighting:

'use client';

import * as React from 'react';
import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider';
import { CodeControllerContext } from '@mui/internal-docs-infra/CodeControllerContext';
import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';

// Simple controller implementation
function CodeController({ children }: { children: React.ReactNode }) {
  const [code, setCode] = React.useState();
  const contextValue = React.useMemo(() => ({ code, setCode }), [code, setCode]);

  return (
    <CodeControllerContext.Provider value={contextValue}>{children}</CodeControllerContext.Provider>
  );
}

// Usage
function CodeEditor() {
  const initialCode = {
    Default: {
      url: 'file://example.js',
      fileName: 'example.js',
      source: `function greet(name) {
  return \`Hello, \${name}!\`;
}`,
    },
  };

  return (
    <CodeProvider>
      <CodeController>
        <CodeHighlighter
          url={initialCode.Default.url}
          code={initialCode}
          controlled
          Content={YourEditorContent}
        />
      </CodeController>
    </CodeProvider>
  );
}

Pattern 2: Live Demo Controller

For live component previews with editable source code:

'use client';

import * as React from 'react';
import { useRunner } from 'react-runner';
import { CodeControllerContext } from '@mui/internal-docs-infra/CodeControllerContext';

function DemoController({ children }: { children: React.ReactNode }) {
  const [code, setCode] = React.useState();

  // Create live component instances from code
  const components = React.useMemo(() => {
    if (!code) return undefined;

    return Object.keys(code).reduce((acc, variant) => {
      const source = code[variant]?.source;
      if (source) {
        acc[variant] = <Runner code={source} />;
      }
      return acc;
    }, {});
  }, [code]);

  const contextValue = React.useMemo(
    () => ({ code, setCode, components }),
    [code, setCode, components],
  );

  return (
    <CodeControllerContext.Provider value={contextValue}>{children}</CodeControllerContext.Provider>
  );
}

Working with useCode Hook

The useCode hook automatically integrates with the CodeController context when the controlled prop is true:

'use client';

import { useEditable } from 'use-editable';
import { useCode } from '@mui/internal-docs-infra/useCode';

function EditorContent(props) {
  const preRef = React.useRef<HTMLPreElement | null>(null);
  const code = useCode(props, { preRef });

  // code.setSource updates the controller automatically
  const onInput = React.useCallback(
    (text: string) => {
      code.setSource?.(text);
    },
    [code],
  );

  useEditable(preRef, onInput, { indentation: 2 });

  return (
    <div>
      <h4>{code.selectedFileName}</h4>
      {code.selectedFile}
    </div>
  );
}

Working with useDemo Hook

The useDemo hook provides additional functionality for live component demos:

'use client';

import { useDemo } from '@mui/internal-docs-infra/useDemo';

function DemoContent(props) {
  const demo = useDemo(props);

  const onInput = React.useCallback(
    (text: string) => {
      demo.setSource?.(text);
    },
    [demo],
  );

  return (
    <div>
      {/* Live component preview */}
      <div>{demo.component}</div>

      {/* Editable source code */}
      <div>
        <select value={demo.selectedVariant} onChange={(e) => demo.selectVariant(e.target.value)}>
          {demo.variants.map((variant) => (
            <option key={variant} value={variant}>
              {variant}
            </option>
          ))}
        </select>

        <textarea value={demo.selectedFile} onChange={(e) => onInput(e.target.value)} />
      </div>
    </div>
  );
}

Data Flow

The CodeController provides a simple state management pattern:

  1. Initial State: Controller starts with empty state (code: undefined)
  2. Code Loading: CodeHighlighter loads initial code from file or props
  3. User Interaction: When users edit code, setSource calls controlledSetCode
  4. State Update: Controller state updates and all connected components re-render
  5. Live Preview: For demo controllers, components are regenerated from updated code
// Flow: User Edit → setSource → controlledSetCode → Context Update → Re-render

function EditorContent(props) {
  const code = useCode(props); // Connects to controller context

  const onEdit = (newSource) => {
    code.setSource(newSource); // This updates the controller context
  };

  // code.selectedFile reflects the current controller state
  return <textarea value={code.selectedFile} onChange={onEdit} />;
}

Integration with createDemo

The createDemo and createLiveDemo functions automatically handle controller integration:

// demos/my-demo/index.ts
import { createDemo } from '@mui/internal-docs-infra/functions/createDemo';
import { MyComponent } from './MyComponent';

export const DemoMyComponent = createDemo(import.meta.url, MyComponent, {
  name: 'My Component Demo',
  slug: 'my-component-demo',
});

When used with a controller, the demo automatically:

  • Loads source code from the filesystem
  • Provides editing capabilities through useCode or useDemo
  • Updates the controller state when code changes
  • Re-renders connected components

Client Dependencies for Live Demos

For live demos that need to run code dynamically (like the Live Demo example), you must provide a ClientProvider to ensure dependencies are properly bundled:

Using createDemoClient

Note

Before you can use createDemoClient in your demos, you must first create this file in your repo using the abstract factory. See the abstractCreateDemoClient docs for details.

Once you've created your own createDemoClient file, you can use it in your demos as shown below:

client.ts
// demos/my-live-demo/client.ts
'use client';

import { createDemoClient } from '../createDemoClient';

const ClientProvider = createDemoClient(import.meta.url);

export default ClientProvider;

Creating a Custom createDemoClient

You can also create your own createDemoClient using the abstract factory:

createDemoClient.ts
'use client';

import { createDemoClientFactory } from '@mui/internal-docs-infra/abstractCreateDemoClient';

/**
* Creates a demo client copying dependencies in the client bundle for live editing.
* @param url Depends on `import.meta.url` to determine the source file location.
* @param meta Additional meta and modules for the demo client.
*/
export const createDemoClient = createDemoClientFactory({
live: true,
});

Integration with createLiveDemo

Then use the ClientProvider in your demo configuration:

index.ts
// demos/my-live-demo/index.ts
import { createLiveDemo } from '../createLiveDemo';
import ClientProvider from './client';
import MyComponent from './MyComponent';

export const DemoMyLiveComponent = createLiveDemo(import.meta.url, MyComponent, {
name: 'My Live Component',
slug: 'my-live-component',
ClientProvider, // Required for dependency hoisting
});

The ClientProvider ensures that:

  • Component dependencies are hoisted into the client bundle
  • External libraries are available for the useRunner hook through CodeExternalsContext
  • Live code execution has access to all required modules via precomputed externals

This is only needed for demos that render live components from editable code, not for simple code editor scenarios.

Best Practices

  1. Use with CodeProvider - Always wrap CodeController with CodeProvider for client-side highlighting
  2. Keep controllers simple - Controllers should only manage state, not complex logic
  3. Let hooks handle integration - Use useCode and useDemo hooks rather than manual context access
  4. Start with empty state - Let the initial code load through the normal CodeHighlighter flow
  5. Use controlled prop - Set controlled={true} on CodeHighlighter components

Context API

CodeControllerContext

The context provides the following interface:

interface CodeControllerContext {
  // Current controlled code state
  code?: ControlledCode;

  // Current selection (variant, file, transform)
  selection?: Selection;

  // Function to update code state
  setCode?: React.Dispatch<React.SetStateAction<ControlledCode | undefined>>;

  // Function to update selection state
  setSelection?: React.Dispatch<React.SetStateAction<Selection>>;

  // Override components for live previews
  components?: Record<string, React.ReactNode>;
}

useControlledCode Hook

Access the context values:

import { useControlledCode } from '@mui/internal-docs-infra/CodeControllerContext';

function MyComponent() {
  const {
    controlledCode,
    controlledSelection,
    controlledSetCode,
    controlledSetSelection,
    controlledComponents,
  } = useControlledCode();

  // Use the values...
}

Types

useControlledCode

Hook to access controlled code state and setters. This is useful for custom components that need to interact with the controlled code state. Use useCode instead when you need access to the code data along with control functions. Use this hook when you need direct access to the setCode and setSelection functions from the CodeControllerContext. It’s worth noting that useCode and useDemo handle controlling selection in typical cases.

Return Type
KeyTypeRequired
codeControlledCode | undefinedYes
selectionSelection | undefinedYes
setCodeDispatch<SetStateAction<ControlledCode | undefined>> | undefinedYes
setSelectionDispatch<SetStateAction<Selection>> | undefinedYes
componentsRecord<string, ReactNode> | undefinedYes

See Types

Requirements

  • CodeProvider: Required for client-side code highlighting and processing
  • Client component: CodeController must be a client component ('use client')
  • Controlled prop: CodeHighlighter components must have controlled={true}
  • ClientProvider: Required for live demos that execute code dynamically (use createDemoClient to create one)

Related Components

Purpose

The CodeControllerContext is specifically designed for interactive code editing scenarios where you need:

  • Real-time code editing with syntax highlighting
  • Live component previews that update as code changes
  • Shared state between multiple code-related components
  • TypeScript/JavaScript transformation toggle

Use this component when building interactive documentation, code playgrounds, tutorials, or any scenario where users need to edit and see live updates to source code.