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.
CodeHighlighter and demo components// 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>
);
}
Type Whatever You Want Below
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>
);
}
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>
);
}
The Code Controller provides a simple React context for managing controlled code state. It works with two main patterns:
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>
);
}
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>
);
}
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>
);
}
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>
);
}
The CodeController provides a simple state management pattern:
code: undefined)CodeHighlighter loads initial code from file or propssetSource calls controlledSetCode// 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} />;
}
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:
useCode or useDemoFor live demos that need to run code dynamically (like the Live Demo example), you must provide a ClientProvider to ensure dependencies are properly bundled:
Note
Before you can use
createDemoClientin your demos, you must first create this file in your repo using the abstract factory. See theabstractCreateDemoClientdocs for details.
Once you've created your own createDemoClient file, you can use it in your demos as shown below:
// demos/my-live-demo/client.ts
'use client';
import { createDemoClient } from '../createDemoClient';
const ClientProvider = createDemoClient(import.meta.url);
export default ClientProvider;You can also create your own createDemoClient using the abstract factory:
'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,
});Then use the ClientProvider in your demo configuration:
// 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:
useRunner hook through CodeExternalsContextThis is only needed for demos that render live components from editable code, not for simple code editor scenarios.
CodeProvider - Always wrap CodeController with CodeProvider for client-side highlightinguseCode and useDemo hooks rather than manual context accessCodeHighlighter flowcontrolled={true} on CodeHighlighter componentsThe 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>;
}
Access the context values:
import { useControlledCode } from '@mui/internal-docs-infra/CodeControllerContext';
function MyComponent() {
const {
controlledCode,
controlledSelection,
controlledSetCode,
controlledSetSelection,
controlledComponents,
} = useControlledCode();
// Use the values...
}
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.
| Key | Type | Required |
|---|---|---|
| code | ControlledCode | undefined | Yes |
| selection | Selection | undefined | Yes |
| setCode | Dispatch<SetStateAction<ControlledCode | undefined>> | undefined | Yes |
| setSelection | Dispatch<SetStateAction<Selection>> | undefined | Yes |
| components | Record<string, ReactNode> | undefined | Yes |
CodeProvider: Required for client-side code highlighting and processing'use client')CodeHighlighter components must have controlled={true}createDemoClient to create one)useCode Hook - Integrates CodeHighlighter with controller stateuseDemo Hook - Extends useCode with live component preview capabilitiesThe CodeControllerContext is specifically designed for interactive code editing scenarios where you need:
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.