MUI Docs Infra

Use Code

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.

Overview

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:

  • URL-aware file navigation with automatic hash management
  • Automatic name and slug generation from URLs when not provided
  • Multi-file code management with seamless file switching
  • Transform support for code modifications (e.g., JS ↔ TS conversion)
  • Clipboard integration with copy functionality
  • Source editing capabilities for interactive code editing

Architecture

The hook is built using a modular architecture with six specialized sub-hooks:

  • useVariantSelection: Manages code variant selection and related data
  • useTransformManagement: Handles code transforms and their application
  • useFileNavigation: Manages file selection within code variants
  • useUIState: Controls UI state like expansion
  • useCopyFunctionality: Handles clipboard operations
  • useSourceEditing: Manages source code editing capabilities

Basic Usage

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>
  );
}

API Reference

Parameters

contentProps: ContentProps<T>

The content properties from your CodeHighlighter component - typically passed directly from props.

opts?: UseCodeOpts

Optional 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
}

Return Value

The hook returns a code object with the following properties:

Variant Management

  • variants: string[] - Array of available variant keys
  • selectedVariant: string - Currently selected variant key
  • selectVariant: React.Dispatch<React.SetStateAction<string>> - Function to change variant

File Navigation

  • files: Array<{ name: string; slug?: string; component: React.ReactNode }> - Available files in current variant with optional URL slugs
  • selectedFile: React.ReactNode - Currently selected file component
  • selectedFileName: string | undefined - Name of currently selected file
  • selectFileName: (fileName: string) => void - Function to select a file (automatically updates URL hash)
    • Progressive Enhancement: Attach to <a> tags with href={"#" + file.slug} for no-JS support
    • The function will call e.preventDefault() after hydration
    • Use CSS :target and :has() selectors to show/hide tabs before JS loads

UI State

  • expanded: boolean - Whether the code view is expanded
  • expand: () => void - Function to expand the code view
  • setExpanded: React.Dispatch<React.SetStateAction<boolean>> - Function to set expansion state

Copy Functionality

  • copy: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void> - Function to copy code to clipboard

Transform Management

  • availableTransforms: string[] - Array of available transform keys
  • selectedTransform: string | null | undefined - Currently selected transform
  • selectTransform: (transformName: string | null) => void - Function to select a transform

Source Editing

  • setSource?: (source: string) => void - Function to update source code (when available)

User Properties

  • userProps: UserProps<T> - Generated user properties including name, slug, and custom props

Advanced Usage

URL Management and File Navigation

The 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>
  );
}

Controlling URL Hash Behavior

The hook provides fine-grained control over URL hash management through two complementary options:

fileHashMode

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

    • Reads URL hash on mount to select the initial file/variant
    • Completely removes the hash when user clicks a file tab
    • Removes the hash when user manually switches variants (e.g., via dropdown)
    • Use case: Demos that support deep linking but clean up URLs after user engagement
  • 'remove-filename': Keep variant in hash on interaction

    • Reads URL hash on mount to select the initial file/variant
    • Simplifies hash to just variant when user clicks a file tab (e.g., #demo:variant:file.tsx#demo:variant)
    • For "Default" variant, simplifies to just the demo slug (e.g., #demo:file.tsx#demo)
    • Preserves variant selection in URL even after user interaction
    • Use case: Demos where variant selection should remain visible in URL

saveHashVariantToLocalStorage

Controls when a hash-specified variant gets saved to localStorage for future visits.

Options:

  • 'on-load': Save immediately when page loads with hash

    • Hash variant is saved to localStorage as soon as the page loads
    • Future visits will default to this variant even without the hash
    • Use case: Persistent variant preference - once a user visits a specific variant, they keep seeing it
  • 'on-interaction' (default): Save only when user interacts

    • Hash variant is NOT saved to localStorage on initial load
    • Only saved when user explicitly clicks a file tab
    • Allows hash navigation without affecting stored preferences
    • Use case: Shareable links shouldn't override user's personal variant preference
  • 'never': Never save hash variant to localStorage

    • Hash-specified variants are never persisted
    • Each visit starts fresh with initialVariant or first variant
    • Use case: Stateless demos where variant selection shouldn't be remembered

Examples

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
}

Automatic Name and Slug Generation

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>;
}

Accessing Transform Functionality

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>
  );
}

Context Integration

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>;

File Navigation

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 useDemo instead, which handles both component rendering and file navigation.

Tip

Progressive Enhancement: Use <a> tags with href attributes for file tabs to enable navigation before JavaScript loads. The selectFileName function will prevent default navigation after hydration, while CSS :target and :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.

URL Hash Patterns

The hook generates URL hashes for file navigation following these patterns:

Default Variant

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

Non-Default Variants

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

Variant-Only Hashes

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.

File Naming Conventions

  • File names are converted to kebab-case while preserving extensions
  • Complex names like ButtonWithTooltip.tsx become button-with-tooltip.tsx
  • Special characters are replaced with dashes
  • Multiple consecutive dashes are collapsed to single dashes

URL Management Behavior

Initial Load:

  • Hash is read to determine initial file and variant selection
  • Priority: URL hash > localStorage > initialVariant > first variant

User Interactions:

  • File Tab Clicks: Hash behavior controlled by fileHashMode option
    • 'remove-hash': Removes entire hash
    • 'remove-filename': Keeps variant in hash (e.g., #demo:variant)
  • Variant Changes (e.g., dropdown): Hash behavior controlled by fileHashMode option
    • 'remove-hash': Removes entire hash
    • 'remove-filename': Updates hash to reflect new variant

localStorage Persistence:

  • Controlled by 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 localStorage

Auto-Expansion:

  • Code demos automatically expand when a relevant hash is present
  • Ensures hash-linked content is visible to users

Sub-hooks Architecture

The useCode hook is composed of several specialized sub-hooks that can be used independently:

useVariantSelection

Manages variant selection logic and provides variant-related data. Implements a priority system for variant selection:

  1. URL hash: Takes precedence when present
  2. localStorage: Used when no hash is present
  3. initialVariant: Fallback when no stored preference
  4. First variant: Final fallback

The hook parses variants from URL hashes and optionally persists them to localStorage based on the saveHashVariantToLocalStorage configuration.

useTransformManagement

Handles code transforms, including delta validation and transform application.

useFileNavigation

Manages file selection and navigation within code variants. Features:

  • URL hash management for deep-linking
  • Automatic slug generation for each file
  • Hash removal behavior controlled by fileHashMode
  • Cross-variant file navigation
  • Detection of user-initiated vs. hash-driven variant changes

The hook generates slugs for all files across all variants, with special handling for the "Default" variant (which omits the variant name from the hash).

useUIState

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.

useCopyFunctionality

Handles clipboard operations and copy state management.

useSourceEditing

Manages source code editing capabilities when available.

Best Practices

1. Leverage Automatic URL Generation

// 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>

2. Handle Deep-Linking and URL States

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>
  );
}

3. Focus on Code Management

// 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>
  );
}

4. Handle Loading States

function SafeCodeInterface(props) {
  const code = useCode(props);

  if (!code.selectedFile) {
    return <div>Loading code...</div>;
  }

  return <div>{code.selectedFile}</div>;
}

5. Leverage Options for Initial State

const code = useCode(props, {
  defaultOpen: true, // Start expanded
  initialVariant: 'TypeScript', // Pre-select variant
  initialTransform: 'js', // Pre-apply transform
});

Performance Considerations

  • The hook uses extensive memoization to prevent unnecessary re-renders
  • Transform computations are cached and only recalculated when necessary
  • File navigation state is optimized for quick switching between files
  • Copy functionality includes debouncing to prevent excessive clipboard operations

Error Handling

The hook includes built-in error handling for:

  • Invalid code structures
  • Missing transforms
  • File navigation errors
  • Copy operation failures

Errors are logged to the console and the hook gracefully degrades functionality when errors occur.

Related

  • useDemo: For managing component rendering alongside code display - use this when you need both code and component functionality
  • CodeHighlighter: The main component this hook is designed to work with

Troubleshooting

Transforms Not Available

Transforms are only shown when they have meaningful changes. Empty transforms are automatically filtered out.

Context Not Working

Ensure your component is used within a CodeHighlighter component that provides the necessary context.

URL Hash Not Working

  • Ensure your component is running in a browser environment (not SSR)
  • Check that file names in your code match the expected slug patterns
  • Verify that the URL hash matches the generated file slugs exactly
  • Use browser dev tools to inspect code.files[].slug values for debugging

Name/Slug Not Generated

  • Ensure a valid url property is provided to CodeHighlighter or available in context
  • Check that the URL follows a recognizable pattern (file paths or simple strings)
  • If URL parsing fails, provide explicit name and slug properties as fallbacks

File Navigation Issues

  • Ensure file names are unique within each variant
  • Check that extraFiles and main files don't have conflicting names
  • Verify transforms don't create duplicate file names

Hash Behavior Not Working as Expected

  • Hash not being removed on interaction: Verify fileHashMode is set to 'remove-hash' (default) or 'remove-filename'
  • Hash removed when you want to keep variant: Use fileHashMode: 'remove-filename' to preserve variant in hash
  • Initial file not selected: Check that the hash format matches the generated slugs:
    • Default variant: mainSlug:fileName
    • Other variants: mainSlug:variant:fileName or mainSlug:variant
  • Variant not persisting: Check saveHashVariantToLocalStorage setting:
    • 'on-load': Saves immediately when page loads with hash
    • 'on-interaction': Saves only when user clicks a tab (default)
    • 'never': Never saves hash variants
  • Hash variant overriding localStorage: This is expected - hash has highest priority. Use saveHashVariantToLocalStorage: 'never' to prevent hash variants from being saved
  • Code not expanding with hash: Ensure useUIState is receiving the mainSlug parameter for auto-expansion