MUI Docs Infra

Warning

This is an internal project, and is not intended for public use. No support or stability guarantees are provided.

Use Code Window

The useCodeWindow hook layers code-block-specific choreography on top of useScrollAnchor so a syntax-highlighted snippet can expand and collapse without the user losing their place — and without the horizontal scrollbar snapping in or out.

It is the recommended hook when you're building a collapsible code block. For non-code layouts, reach for useScrollAnchor directly.

This hook assumes a page-level layout: it always compensates window scroll, and the offscreen check it uses to decide between anchors is measured against the window viewport. It does not currently expose a scrollContainerRef — if your code block lives inside its own scroll container, drop down to useScrollAnchor.

What it does

On top of useScrollAnchor's page-scroll compensation, this hook:

  1. Picks the anchor for you. Looks for an element matching [data-frame-type="highlighted"], [data-frame-type="focus"] inside the container (the convention used by the highlighter pipeline). Falls back to the toggle ref when no such frame exists, or when the highlighted frame is offscreen on collapse (offscreen is measured against the window viewport). If neither anchor is available, anchorScroll no-ops.
  2. Animates the scrollbar gutter. Toggles a data-scrollbar-gutter attribute on the inner <pre> so your CSS can swap between a real horizontal scrollbar and equivalent padding-bottom instead of snapping when overflow appears or disappears. The CSS transition runs regardless of prefers-reduced-motion — if you want to honor it, gate the transition in your stylesheet.
  3. Slides scroll back to column 0 on collapse. Translates the inner <code> element back to scrollLeft = 0 via a compositor-driven WAAPI animation, so the focused region (which usually starts at the left edge) is back in view after collapsing. Honors prefers-reduced-motion.
  4. Cancels the most recent <pre> animation on unmount. Earlier <pre> animations (if containerRef was reattached to a different element during the component's lifetime) are left to garbage collection.

Basic Usage

Snippet

function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  const pivot = arr[0];
  const left = [];
  const right = [];
  for (let index = 1; index < arr.length; index += 1) {
    if (arr[index] < pivot) {
      left.push(arr[index]);
    } else {
      right.push(arr[index]);
    }
  }
  return [...quickSort(left), pivot, ...quickSort(right)];
}
'use client';
import * as React from 'react';
import { useCodeWindow } from '@mui/internal-docs-infra/useCodeWindow';
import styles from './CollapsibleSnippet.module.css';

const longSource = `function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  const pivot = arr[0];
  const left = [];
  const right = [];
  for (let index = 1; index < arr.length; index += 1) {
    if (arr[index] < pivot) {
      left.push(arr[index]);
    } else {
      right.push(arr[index]);
    }
  }
  return [...quickSort(left), pivot, ...quickSort(right)];
}`;

export function CollapsibleSnippet() {
  const [expanded, setExpanded] = React.useState(false);
  const { containerRef, toggleRef, anchorScroll } = useCodeWindow<HTMLButtonElement>();

  const onToggle = () => {
    // Anchor *before* the layout-changing state update.
    anchorScroll(expanded ? 'collapse' : 'expand');
    setExpanded((prev) => !prev);
  };

  return (
    <div ref={containerRef} className={styles.container}>
      <pre className={`${styles.pre} ${expanded ? styles.expanded : ''}`}>
        <code className={styles.code}>{longSource}</code>
      </pre>
      <button ref={toggleRef} type="button" onClick={onToggle} className={styles.toggle}>
        {expanded ? 'Collapse' : 'Expand'}
      </button>
    </div>
  );
}
import { useCodeWindow } from '@mui/internal-docs-infra/useCodeWindow';

function CollapsibleSnippet() {
  const [expanded, setExpanded] = React.useState(false);
  const { containerRef, toggleRef, anchorScroll } = useCodeWindow<HTMLButtonElement>();

  return (
    <div ref={containerRef}>
      <pre className={expanded ? 'expanded' : ''}>
        <code>{source}</code>
      </pre>
      <button
        ref={toggleRef}
        type="button"
        onClick={() => {
          // Anchor before the layout-changing state update.
          anchorScroll(expanded ? 'collapse' : 'expand');
          setExpanded((prev) => !prev);
        }}
      >
        {expanded ? 'Collapse' : 'Expand'}
      </button>
    </div>
  );
}

The hook expects a structure of <div ref={containerRef}><pre><code></code></pre></div>. The <pre> and <code> elements are how the gutter and scroll-back animations find their target — the hook locates them by querying the container's DOM (the first <pre> descendant wins), so no extra refs are needed. anchorScroll('expand' | 'collapse') uses the configured expandDuration / collapseDuration, so there's no need to pass a duration at the call site.

The single generic types the toggle element (HTMLButtonElement here), not the container — unlike useScrollAnchor, where the first generic types containerRef. The container is always typed as HTMLDivElement because the hook needs to query into it.

Configuration

Defaults match the highlighter's CSS conventions, but every opinion is configurable:

useCodeWindow({
  expandDuration: 350, // ms — must match your CSS expand transition
  collapseDuration: 350, // ms — must match your CSS collapse transition
  scrollBackDuration: 300, // ms — set to 0 to disable
  anchorSelector: '[data-frame-type="highlighted"], [data-frame-type="focus"]',
  collapsibleProbeSelector: '[data-collapsible]',
});

expandDuration / collapseDuration

Default 350 ms each. Should equal the CSS transition duration on whichever property drives the height change (height with interpolate-size, max-height, etc.). The hook keeps the page-scroll compensation alive for this long, then hands control back to the user.

scrollBackDuration

Default 300 ms. The horizontal <code>-transform animation that returns the snippet to column 0 on collapse. The default matches the gutter animation; set to 0 to disable.

anchorSelector

Selector evaluated inside the container to pick the anchor. The first match wins. Defaults to [data-frame-type="highlighted"], [data-frame-type="focus"] — override only if your markup uses different data attributes.

collapsibleProbeSelector

Defaults to [data-collapsible]. When set, the expand transition only animates the gutter if at least one element matching this selector exists inside the <pre>. This avoids unnecessary attribute toggling for plain (non-truncated) snippets where no scrollbar will appear after expansion. Pass a falsy value (undefined or false) to skip the gutter animation on expand entirely.

CSS contract

The hook flips a data-scrollbar-gutter attribute on the inner <pre>. To get the gutter animation, your stylesheet must provide the corresponding rules. The example below references a --scrollbar-h custom property that you must define yourself — either via scrollbar-gutter: stable math, a JS-set value on :root, or a measured value from your design system. The hook does not expose its internal scrollbar measurement.

/* Just before collapse: lock overflow and reserve the scrollbar's space */
pre[data-scrollbar-gutter='collapse-from'] {
  overflow-x: hidden;
  padding-bottom: var(--scrollbar-h);
}
/* Animate to the resting padding */
pre[data-scrollbar-gutter='collapse-to'] {
  overflow-x: hidden;
  padding-bottom: 12px;
  transition: padding-bottom 0.35s ease;
}
/* Mirror for expand if you want the scrollbar to fade in */
pre[data-scrollbar-gutter='expand-from'] {
  overflow-x: hidden;
  padding-bottom: 12px;
}
pre[data-scrollbar-gutter='expand-to'] {
  overflow-x: hidden;
  padding-bottom: var(--scrollbar-h);
  transition: padding-bottom 0.35s ease;
}

Without these rules the attribute is harmless — it just toggles silently. If you change expandDuration / collapseDuration, update the 0.35s values above to match.

Examples

These demos all use enhanceCodeEmphasis directives (@highlight, @focus, @padding) to mark which lines belong to the focus area. useCodeWindow then handles the choreography — picking the anchor, animating the gutter, and keeping your scroll position pinned.

Collapsible Code Block

A code block that starts collapsed, revealing only the highlighted and padding frames. Click Expand to show the full source.

Collapsible Code Block

import * as React from 'react';
import { fetchUser } from './api';

interface User {
  name: string;
  email: string;
}

export function UserProfile({ id }: { id: string }) {
  const [user, setUser] = React.useState<User | null>(null);

  React.useEffect(() => {
    let cancelled = false;
    fetchUser(id).then((data) => {
      if (!cancelled) {
        setUser(data);
      }
    });
    return () => {
      cancelled = true;
    };
  }, [id]);

  if (!user) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}
import * as React from 'react';
import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types';
import { parseImportsAndComments } from '@mui/internal-docs-infra/pipeline/loaderUtils';
import { EMPHASIS_COMMENT_PREFIX } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
import { Code } from './Code';

const source = `import * as React from 'react';
import { fetchUser } from './api';

interface User {
  name: string;
  email: string;
}

export function UserProfile({ id }: { id: string }) {
  const [user, setUser] = React.useState<User | null>(null);

  // @highlight-start
  React.useEffect(() => {
    let cancelled = false;
    fetchUser(id).then((data) => {
      if (!cancelled) {
        setUser(data);
      }
    });
    return () => {
      cancelled = true;
    };
  }, [id]);
  // @highlight-end

  if (!user) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}`;

export async function CollapsibleCode() {
  const { code: strippedSource, comments } = await parseImportsAndComments(source, '/demo.tsx', {
    removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX],
    notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX],
  });

  const code: CodeType = {
    Default: {
      language: 'tsx',
      source: strippedSource!,
      comments,
    },
  };

  return <Code code={code} />;
}

Collapsible Demo

A full demo component with a live preview, file tabs, and collapsible code. The code section starts collapsed, showing only the focused region.

Collapsible Demo

Counter.tsx
'use client';

import * as React from 'react';

export function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <button type="button" onClick={() => setCount((c) => c + 1)}>
      Count: {count}
    </button>
  );
}
import { Counter } from './Counter';
import { createDemo } from './createDemo';

export const DemoCounter = createDemo(import.meta.url, Counter);

@focus with Multiple Regions

Uses @highlight on the formatDate call and standalone @focus-start/@focus-end around the useEffect block. The useEffect region is focused (padding frames surround it) but has no line-level highlighting, while the formatDate line has line-level highlighting but is unfocused.

@focus Directive

import * as React from 'react';
import { formatDate } from './formatDate';
import { fetchEvents } from './fetchEvents';

const today = formatDate(new Date());

export function Calendar() {
  const [events, setEvents] = React.useState([]);

  React.useEffect(() => {
    fetchEvents(today).then(setEvents);
  }, []);

  return (
    <div className="calendar">
      <h2>{today}</h2>
      <ul>
        {events.map((event) => (
          <li key={event.id}>{event.title}</li>
        ))}
      </ul>
    </div>
  );
}
import * as React from 'react';
import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types';
import { parseImportsAndComments } from '@mui/internal-docs-infra/pipeline/loaderUtils';
import {
  EMPHASIS_COMMENT_PREFIX,
  FOCUS_COMMENT_PREFIX,
} from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
import { Code } from './Code';

const source = `import * as React from 'react';
import { formatDate } from './formatDate';
import { fetchEvents } from './fetchEvents';

const today = formatDate(new Date()); // @highlight

export function Calendar() {
  const [events, setEvents] = React.useState([]);

  // @focus-start
  React.useEffect(() => {
    fetchEvents(today).then(setEvents);
  }, []);
  // @focus-end

  return (
    <div className="calendar">
      <h2>{today}</h2>
      <ul>
        {events.map((event) => (
          <li key={event.id}>{event.title}</li>
        ))}
      </ul>
    </div>
  );
}`;

export async function FocusCode() {
  const { code: strippedSource, comments } = await parseImportsAndComments(source, '/demo.tsx', {
    removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX, FOCUS_COMMENT_PREFIX],
    notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX, FOCUS_COMMENT_PREFIX],
  });

  const code: CodeType = {
    Default: {
      language: 'tsx',
      source: strippedSource!,
      comments,
    },
  };

  return <Code code={code} />;
}

Indent Shifting

With no padding configured, only highlighted/focus and normal frames are produced. Opt in to the data-frame-indent attribute on region frames by passing emitFrameIndent: true to createEnhanceCodeEmphasis — CSS can then read it to know how far the code is indented. This demo highlights an import statement (indent 0) and uses standalone @focus-start/@focus-end around the deeply nested <DatePicker> usage (indent 3). When collapsed, the focused frame shifts left by 6ch (3 indent levels × 2ch per level) so it aligns with the left edge, then resets when expanded.

Indent Shifting

import * as React from 'react';
import { DatePicker } from './DatePicker';

export function ScheduleView() {
  const [date, setDate] = React.useState(null);

  return (
    <main>
      <header>
        <h1>Schedule</h1>
      </header>
      <section>
        <form>
          <label htmlFor="date">Pick a date</label>
          <DatePicker
            id="date"
            value={date}
            onChange={setDate}
            minDate={new Date()}
            format="MM/dd/yyyy"
          />
        </form>
      </section>
      <footer>
        <p>All times shown in UTC</p>
      </footer>
    </main>
  );
}
import * as React from 'react';
import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types';
import { parseImportsAndComments } from '@mui/internal-docs-infra/pipeline/loaderUtils';
import {
  EMPHASIS_COMMENT_PREFIX,
  FOCUS_COMMENT_PREFIX,
} from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
import { CodeIndent } from './CodeIndent';

const source = `import * as React from 'react';
import { DatePicker } from './DatePicker'; // @highlight

export function ScheduleView() {
  const [date, setDate] = React.useState(null);

  return (
    <main>
      <header>
        <h1>Schedule</h1>
      </header>
      <section>
        <form>
          <label htmlFor="date">Pick a date</label>
          {/* @focus-start */}
          <DatePicker
            id="date"
            value={date}
            onChange={setDate}
            minDate={new Date()}
            format="MM/dd/yyyy"
          />
          {/* @focus-end */}
        </form>
      </section>
      <footer>
        <p>All times shown in UTC</p>
      </footer>
    </main>
  );
}`;

export async function IndentCode() {
  const { code: strippedSource, comments } = await parseImportsAndComments(source, '/demo.tsx', {
    removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX, FOCUS_COMMENT_PREFIX],
    notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX, FOCUS_COMMENT_PREFIX],
  });

  const code: CodeType = {
    Default: {
      language: 'tsx',
      source: strippedSource!,
      comments,
    },
  };

  return <CodeIndent code={code} />;
}

Focus Max Size

When focusFramesMaxSize is set and a highlighted region exceeds that limit, a focused window is taken from the start of the region, and the remaining overflow lines are marked as unfocused. This demo uses focusFramesMaxSize: 6 with the <form> JSX highlighted (10 lines). When collapsed, only the first 6 lines of the region are visible; expanding reveals the full highlighted region with the overflow lines.

Focus Max Size

import * as React from 'react';

interface FormData {
  name: string;
  email: string;
  message: string;
}

export function ContactForm() {
  const [form, setForm] = React.useState<FormData>({
    name: '',
    email: '',
    message: '',
  });

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    const response = await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(form),
    });
    if (!response.ok) {
      throw new Error('Failed to submit');
    }
    setForm({ name: '', email: '', message: '' });
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
      <label htmlFor="email">Email</label>
      <input id="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} />
      <label htmlFor="message">Message</label>
      <textarea id="message" value={form.message} onChange={(e) => setForm({ ...form, message: e.target.value })} />
      <button type="submit">Send</button>
    </form>
  );
}
import * as React from 'react';
import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types';
import { parseImportsAndComments } from '@mui/internal-docs-infra/pipeline/loaderUtils';
import { EMPHASIS_COMMENT_PREFIX } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
import { CodeMaxSize } from './CodeMaxSize';

const source = `import * as React from 'react';

interface FormData {
  name: string;
  email: string;
  message: string;
}

export function ContactForm() {
  const [form, setForm] = React.useState<FormData>({
    name: '',
    email: '',
    message: '',
  });

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    const response = await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(form),
    });
    if (!response.ok) {
      throw new Error('Failed to submit');
    }
    setForm({ name: '', email: '', message: '' });
  };

  return (
    // @highlight-start
    <form onSubmit={handleSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
      <label htmlFor="email">Email</label>
      <input id="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} />
      <label htmlFor="message">Message</label>
      <textarea id="message" value={form.message} onChange={(e) => setForm({ ...form, message: e.target.value })} />
      <button type="submit">Send</button>
    </form>
    // @highlight-end
  );
}`;

export async function MaxSizeCode() {
  const { code: strippedSource, comments } = await parseImportsAndComments(source, '/demo.tsx', {
    removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX],
    notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX],
  });

  const code: CodeType = {
    Default: {
      language: 'tsx',
      source: strippedSource!,
      comments,
    },
  };

  return <CodeMaxSize code={code} />;
}

API Reference

Layered helper that combines useScrollAnchor with the additional choreography needed when expanding/collapsing a syntax-highlighted code block.

On top of the page-scroll compensation provided by useScrollAnchor, it:

  • Selects an anchor inside the container (highlighted or focus frame), falling back to the toggle when the primary anchor is offscreen on collapse.
  • Drives a data-scrollbar-gutter attribute on the inner <pre> so the consumer’s CSS can swap between a real horizontal scrollbar and equivalent padding-bottom without a snap.
  • Smoothly returns the <code> element’s scrollLeft to 0 on collapse via a compositor-driven transform, so the focused region (which usually starts at column 0) is back in view after collapse.

The hook expects a structure like:

<div ref={containerRef}>
  <pre>
    <code>...</code>
  </pre>
  <button ref={toggleRef}>Expand</button>
</div>

Anchor selection and the collapsible probe are configurable so it works with any highlighter that marks frames with data attributes.

PropertyTypeDescription
expandDuration
number | undefined

Duration of the expand transition in ms. Should match the CSS transition on the collapsible container. Used to size the page-scroll compensation window.

collapseDuration
number | undefined

Duration of the collapse transition in ms. Should match the CSS transition on the collapsible container.

scrollBackDuration
number | undefined

Duration of the smooth scroll-back animation that returns the <code> element’s scrollLeft to 0 on collapse. Set to 0 to disable. Honors prefers-reduced-motion.

anchorSelector
string | undefined

CSS selector(s) used to find the anchor element inside the container. The first match wins. Falls back to the toggle ref when no match exists, or when the match is offscreen on collapse.

collapsibleProbeSelector
string | undefined

CSS selector that, when present inside the <pre>, opts the expand transition into the scrollbar-gutter animation. Useful when expansion can reveal previously hidden long lines and the horizontal scrollbar would otherwise appear with a snap.

Return Type
UseCodeWindowResult
KeyTypeRequired
containerRef
React.RefObject<HTMLDivElement | null>
Yes
toggleRef
React.RefObject<HTMLElement | null>
Yes
anchorScroll
(direction: 'collapse' | 'expand') => void
Yes
UseCodeWindowOptions
type UseCodeWindowOptions = {
  /**
   * Duration of the expand transition in ms. Should match the CSS
   * transition on the collapsible container. Used to size the
   * page-scroll compensation window.
   * @default 350
   */
  expandDuration?: number;
  /**
   * Duration of the collapse transition in ms. Should match the CSS
   * transition on the collapsible container.
   * @default 350
   */
  collapseDuration?: number;
  /**
   * Duration of the smooth scroll-back animation that returns the
   * `<code>` element's `scrollLeft` to `0` on collapse. Set to `0`
   * to disable. Honors `prefers-reduced-motion`.
   * @default 300
   */
  scrollBackDuration?: number;
  /**
   * CSS selector(s) used to find the anchor element inside the
   * container. The first match wins. Falls back to the toggle ref
   * when no match exists, or when the match is offscreen on collapse.
   * @default '[data-frame-type="highlighted"], [data-frame-type="focus"]'
   */
  anchorSelector?: string;
  /**
   * CSS selector that, when present inside the `<pre>`, opts the
   * expand transition into the scrollbar-gutter animation. Useful
   * when expansion can reveal previously hidden long lines and the
   * horizontal scrollbar would otherwise appear with a snap.
   * @default '[data-collapsible]'
   */
  collapsibleProbeSelector?: string;
}
UseCodeWindowResult
type UseCodeWindowResult<ToggleElement extends HTMLElement = HTMLElement> = {
  /** Ref to attach to the collapsible container element. */
  containerRef: React.RefObject<HTMLDivElement | null>;
  /**
   * Ref to attach to the toggle element. Used as a fallback anchor
   * when the primary anchor is offscreen on collapse.
   */
  toggleRef: React.RefObject<ToggleElement | null>;
  /**
   * Call **just before** flipping the expanded/collapsed state. The
   * page will scroll so the anchor element stays put while the
   * container animates.
   */
  anchorScroll: (direction: 'collapse' | 'expand') => void;
}
  • useScrollAnchor — the underlying page-scroll compensation primitive. Use directly for non-code layouts.
  • enhanceCodeEmphasis — adds the [data-frame-type] markers this hook anchors to.