The useCode hook provides programmatic access to code display, editing, and transformation functionality within CodeHighlighter components. It's designed for scenarios where you need fine-grained control over code behavior or want to build custom code interfaces that focus purely on code management, without component rendering.
The hook implements the Props Context Layering pattern to work seamlessly across server and client boundaries, automatically merging initial props with enhanced context values.
The useCode hook orchestrates multiple specialized sub-hooks to provide a complete code management solution. It automatically integrates with CodeHighlighterContext when available, making it perfect for custom code interfaces that need to interact with the broader code highlighting system.
Key features include:
The hook is built using a modular architecture with six specialized sub-hooks:
import { useCode } from '@mui/internal-docs-infra';
import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types';
function CodeContent(props: ContentProps<{}>) {
const code = useCode(props, {
defaultOpen: true,
initialVariant: 'TypeScript',
});
return (
<div>
<h3>{code.userProps.name}</h3>
<div>
Current: {code.selectedVariant}
<select value={code.selectedVariant} onChange={(e) => code.selectVariant(e.target.value)}>
{code.variants.map((variant) => (
<option key={variant} value={variant}>
{variant}
</option>
))}
</select>
</div>
{/* File navigation with URL hash support */}
{code.files.length > 1 && (
<div>
{code.files.map((file) => (
<button
key={file.name}
onClick={() => code.selectFileName(file.name)}
className={code.selectedFileName === file.name ? 'active' : ''}
>
{file.name}
</button>
))}
</div>
)}
{code.selectedFile}
<button onClick={code.copy}>Copy Code</button>
</div>
);
}
contentProps: ContentProps<T>The content properties from your CodeHighlighter component - typically passed directly from props.
opts?: UseCodeOptsOptional configuration object for customizing hook behavior.
interface UseCodeOpts {
defaultOpen?: boolean; // Whether to start expanded
copy?: any; // Copy functionality options
initialVariant?: string; // Initially selected variant
initialTransform?: string; // Initially selected transform
fileHashMode?: 'remove-hash' | 'remove-filename'; // Controls hash removal on user interaction
saveHashVariantToLocalStorage?: 'on-load' | 'on-interaction' | 'never'; // When to persist hash variant
}
The hook returns a code object with the following properties:
variants: string[] - Array of available variant keysselectedVariant: string - Currently selected variant keyselectVariant: React.Dispatch<React.SetStateAction<string>> - Function to change variantfiles: Array<{ name: string; slug?: string; component: React.ReactNode }> - Available files in current variant with optional URL slugsselectedFile: React.ReactNode - Currently selected file componentselectedFileName: string | undefined - Name of currently selected fileselectFileName: (fileName: string) => void - Function to select a file (automatically updates URL hash)
<a> tags with href={"#" + file.slug} for no-JS supporte.preventDefault() after hydration:target and :has() selectors to show/hide tabs before JS loadsexpanded: boolean - Whether the code view is expandedexpand: () => void - Function to expand the code viewsetExpanded: React.Dispatch<React.SetStateAction<boolean>> - Function to set expansion statecopy: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void> - Function to copy code to clipboardavailableTransforms: string[] - Array of available transform keysselectedTransform: string | null | undefined - Currently selected transformselectTransform: (transformName: string | null) => void - Function to select a transformsetSource?: (source: string) => void - Function to update source code (when available)userProps: UserProps<T> - Generated user properties including name, slug, and custom propsThe useCode hook automatically manages URL hashes to reflect the currently selected file. This provides deep-linking capabilities and preserves navigation state across page reloads.
function CodeViewer(props) {
const code = useCode(props, {
initialVariant: 'TypeScript',
});
// File selection automatically updates URL hash without polluting browser history
// URLs follow pattern: #mainSlug:fileName or #mainSlug:variant:fileName
return (
<div>
{code.files.map((file) => (
<button
key={file.name}
onClick={() => code.selectFileName(file.name)}
className={code.selectedFileName === file.name ? 'active' : ''}
>
{file.name}
</button>
))}
{/* File slug can be used for sharing or bookmarking */}
<span>
Current file URL: #{code.files.find((f) => f.name === code.selectedFileName)?.slug}
</span>
{code.selectedFile}
</div>
);
}
The hook provides fine-grained control over URL hash management through two complementary options:
Controls what happens to the URL hash when users interact with file tabs or switch variants.
Options:
'remove-hash' (default): Complete hash removal on interaction
'remove-filename': Keep variant in hash on interaction
#demo:variant:file.tsx → #demo:variant)#demo:file.tsx → #demo)Controls when a hash-specified variant gets saved to localStorage for future visits.
Options:
'on-load': Save immediately when page loads with hash
'on-interaction' (default): Save only when user interacts
'never': Never save hash variant to localStorage
Default behavior - Remove hash on interaction:
function StandardCodeViewer(props) {
const code = useCode(props, {
fileHashMode: 'remove-hash', // Can be omitted as it's the default
saveHashVariantToLocalStorage: 'on-interaction', // Can be omitted as it's the default
});
// User visits #demo:variant:file.tsx - that file is selected
// User clicks another file tab - hash is removed completely
// Variant is saved to localStorage (will be default for future visits)
}
Keep variant in URL after interaction:
function VariantAwareCodeViewer(props) {
const code = useCode(props, {
fileHashMode: 'remove-filename',
});
// User visits #demo:typescript:file.tsx - that file in TypeScript variant is selected
// User clicks another file tab - hash becomes #demo:typescript
// Variant remains visible in URL for easy sharing
// Variant is saved to localStorage (will be default for future visits)
}
Hash navigation without affecting preferences:
function ShareableCodeViewer(props) {
const code = useCode(props, {
fileHashMode: 'remove-hash',
saveHashVariantToLocalStorage: 'never',
});
// User visits #demo:variant:file.tsx - that file is selected
// User clicks another file tab - hash is removed
// Variant is NOT saved - next visit uses their preferred variant from localStorage
// Perfect for shareable links that don't override personal preferences
}
Persistent variant preference from first visit:
function StickyVariantCodeViewer(props) {
const code = useCode(props, {
fileHashMode: 'remove-filename',
saveHashVariantToLocalStorage: 'on-load',
});
// User visits #demo:tailwind:file.tsx - Tailwind variant is immediately saved
// Future visits default to Tailwind variant even without hash
// Hash is simplified to #demo:tailwind when user clicks tabs
// Perfect for documentation where first variant choice should stick
}
When name or slug properties are not provided, the hook automatically generates them from the url property (or context URL). This is particularly useful when working with file-based demos or dynamic content.
// Component with explicit name and slug
<CodeHighlighter
name="Custom Button"
slug="custom-button"
Content={MyCodeContent}
>
{/* code */}
</CodeHighlighter>
// Component with automatic generation from URL
<CodeHighlighter
url="file:///app/components/demos/advanced-table/index.ts"
Content={MyCodeContent}
>
{/*
Automatically generates:
- name: "Advanced Table"
- slug: "advanced-table"
*/}
</CodeHighlighter>
function MyCodeContent(props) {
const code = useCode(props);
// Access generated user properties
console.log(code.userProps.name); // "Advanced Table"
console.log(code.userProps.slug); // "advanced-table"
return <div>{/* render code */}</div>;
}
function TransformSelector(props) {
const code = useCode(props, {
initialTransform: 'typescript',
});
return (
<div>
{code.availableTransforms.length > 0 && (
<select
value={code.selectedTransform || ''}
onChange={(e) => code.selectTransform(e.target.value || null)}
>
<option value="">No Transform</option>
{code.availableTransforms.map((transform) => (
<option key={transform} value={transform}>
{transform}
</option>
))}
</select>
)}
{code.selectedFile}
</div>
);
}
The hook automatically integrates with CodeHighlighterContext when used as a Content component:
// Simple wrapper component using CodeHighlighter directly
export function Code({ children, fileName }: { children: string; fileName?: string }) {
return (
<CodeHighlighter
fileName={fileName}
Content={CodeContent} // Your custom content component using useCode
sourceParser={createParseSource()}
>
{children}
</CodeHighlighter>
);
}
// Your custom content component using useCode
function CodeContent(props: ContentProps<{}>) {
// Automatically receives code from CodeHighlighter context
const code = useCode(props);
return (
<div>
{code.selectedFile}
<button onClick={code.copy}>Copy</button>
</div>
);
}
// Usage - simple and direct
<Code fileName="example.ts">
{`function hello() {
console.log('Hello, world!');
}`}
</Code>;
When your code has multiple files, you should provide navigation between them. The recommended approach is to use conditional display - only show tabs when multiple files exist, otherwise show just the filename:
Note
If you're creating demos that combine component previews with multi-file code examples, consider using
useDemoinstead, which handles both component rendering and file navigation.
Tip
Progressive Enhancement: Use
<a>tags withhrefattributes for file tabs to enable navigation before JavaScript loads. TheselectFileNamefunction will prevent default navigation after hydration, while CSS:targetand:has()selectors can show/hide content based on the URL hash.If a user clicks a tab before JavaScript loads, the browser navigates to the hash URL. When the page hydrates, the hook reads this hash and updates the React state accordingly - no clicks are lost during the transition from CSS-only to JavaScript-controlled navigation.
function CodeWithTabs(props) {
const code = useCode(props, { preClassName: styles.codeBlock });
return (
<div className={styles.container}>
<div className={styles.header}>
{code.files.length > 1 ? (
<div className={styles.tabContainer}>
{code.files.map((file) => (
<a
key={file.name}
href={`#${file.slug}`}
onClick={(e) => {
e.preventDefault();
code.selectFileName(file.name);
}}
className={`${styles.tab} ${
code.selectedFileName === file.name ? styles.active : ''
}`}
>
{file.name}
</a>
))}
</div>
) : (
<span className={styles.fileName}>{code.selectedFileName}</span>
)}
</div>
<div className={styles.codeContent}>
{code.files.map((file) => (
<div
key={file.name}
id={file.slug}
className={`${styles.codeFile} ${
code.selectedFileName === file.name ? styles.selected : ''
}`}
>
{file.component}
</div>
))}
</div>
</div>
);
}
CSS for Progressive Enhancement:
/* Hide all files by default */
.codeFile {
display: none;
}
/* Show first file when no hash target exists */
.codeContent:not(:has(.codeFile:target)) .codeFile:first-of-type {
display: block;
}
/* Show targeted file when hash exists */
.codeFile:target {
display: block;
}
/* After JS loads, let React control visibility */
.container:has(.tab.active) .codeFile {
display: none; /* Disable CSS-based visibility when JS is active */
}
.container:has(.tab.active) .codeFile.selected {
display: block; /* React-controlled selection */
}
This pattern ensures a clean user experience by avoiding unnecessary tab UI when only one file exists.
The hook generates URL hashes for file navigation following these patterns:
When the variant name is "Default", it's omitted from the hash:
#mainSlug:fileName
# Examples:
#button-demo:button.tsx
#data-table:index.ts
#advanced-form:styles.css
All other variants include the variant name in the hash:
#mainSlug:variantName:fileName
# Examples:
#button-demo:tailwind:button.tsx
#data-table:typescript:index.ts
#advanced-form:styled-components:styles.css
Variants (except "Default") can also be referenced without a filename:
#mainSlug:variantName
# Examples:
#button-demo:tailwind
#data-table:typescript
#advanced-form:styled-components
These variant-only hashes select the main file of that variant.
ButtonWithTooltip.tsx become button-with-tooltip.tsxInitial Load:
initialVariant > first variantUser Interactions:
fileHashMode option
'remove-hash': Removes entire hash'remove-filename': Keeps variant in hash (e.g., #demo:variant)fileHashMode option
'remove-hash': Removes entire hash'remove-filename': Updates hash to reflect new variantlocalStorage Persistence:
saveHashVariantToLocalStorage option'on-load': Hash variant saved immediately when page loads'on-interaction': Hash variant saved only when user clicks a file tab'never': Hash variant never saved to localStorageAuto-Expansion:
The useCode hook is composed of several specialized sub-hooks that can be used independently:
Manages variant selection logic and provides variant-related data. Implements a priority system for variant selection:
The hook parses variants from URL hashes and optionally persists them to localStorage based on the saveHashVariantToLocalStorage configuration.
Handles code transforms, including delta validation and transform application.
Manages file selection and navigation within code variants. Features:
fileHashModeThe hook generates slugs for all files across all variants, with special handling for the "Default" variant (which omits the variant name from the hash).
Controls UI-related state like expansion management. Automatically expands code demos when a relevant URL hash is present, ensuring hash-linked content is immediately visible to users.
Handles clipboard operations and copy state management.
Manages source code editing capabilities when available.
// Recommended: Let the hook generate name/slug from URL
<CodeHighlighter
url="file:///components/demos/advanced-search/index.ts"
Content={CodeContent}
>
{/* Automatically gets name: "Advanced Search", slug: "advanced-search" */}
</CodeHighlighter>
// Override only when needed
<CodeHighlighter
url="file:///components/demos/search/index.ts"
name="Custom Search Component" // Override auto-generated name
Content={CodeContent}
>
{/* Uses custom name, but auto-generated slug: "search" */}
</CodeHighlighter>
function CodeViewer(props) {
const code = useCode(props, {
initialVariant: 'TypeScript', // Fallback if no URL hash
});
// URL hash automatically handled - no manual intervention needed
// Users can bookmark specific files and return to them
return (
<div>
{code.files.map((file) => (
<button
key={file.name}
onClick={() => code.selectFileName(file.name)}
data-slug={file.slug} // Available for analytics or debugging
>
{file.name}
</button>
))}
{code.selectedFile}
</div>
);
}
// Recommended: Use for code-specific functionality
function CodeSelector(props) {
const code = useCode(props);
return (
<div>
<VariantSelector
variants={code.variants}
selected={code.selectedVariant}
onSelect={code.selectVariant}
/>
{code.selectedFile}
</div>
);
}
function SafeCodeInterface(props) {
const code = useCode(props);
if (!code.selectedFile) {
return <div>Loading code...</div>;
}
return <div>{code.selectedFile}</div>;
}
const code = useCode(props, {
defaultOpen: true, // Start expanded
initialVariant: 'TypeScript', // Pre-select variant
initialTransform: 'js', // Pre-apply transform
});
The hook includes built-in error handling for:
Errors are logged to the console and the hook gracefully degrades functionality when errors occur.
Transforms are only shown when they have meaningful changes. Empty transforms are automatically filtered out.
Ensure your component is used within a CodeHighlighter component that provides the necessary context.
code.files[].slug values for debuggingurl property is provided to CodeHighlighter or available in contextname and slug properties as fallbacksextraFiles and main files don't have conflicting namesfileHashMode is set to 'remove-hash' (default) or 'remove-filename'fileHashMode: 'remove-filename' to preserve variant in hashmainSlug:fileNamemainSlug:variant:fileName or mainSlug:variantsaveHashVariantToLocalStorage setting:
'on-load': Saves immediately when page loads with hash'on-interaction': Saves only when user clicks a tab (default)'never': Never saves hash variantssaveHashVariantToLocalStorage: 'never' to prevent hash variants from being saveduseUIState is receiving the mainSlug parameter for auto-expansion