Warning
This is an internal project, and is not intended for public use. No support or stability guarantees are provided.
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.
On top of useScrollAnchor's page-scroll compensation, this hook:
[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.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.<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.<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.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.
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 / collapseDurationDefault 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.
scrollBackDurationDefault 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.
anchorSelectorSelector 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.
collapsibleProbeSelectorDefaults 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.
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.
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.
A code block that starts collapsed, revealing only the highlighted and padding frames. Click Expand to show the full 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);
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} />;
}
A full demo component with a live preview, file tabs, and collapsible code. The code section starts collapsed, showing only the focused region.
'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 RegionsUses @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.
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} />;
}
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.
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} />;
}
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.
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} />;
}
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:
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.<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.
| Property | Type | Description |
|---|---|---|
| expandDuration | | 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 | | Duration of the collapse transition in ms. Should match the CSS transition on the collapsible container. |
| scrollBackDuration | | Duration of the smooth scroll-back animation that returns the
|
| anchorSelector | | 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 | | CSS selector that, when present inside the |
UseCodeWindowResult| Key | Type | Required |
|---|---|---|
| containerRef | | Yes |
| toggleRef | | Yes |
| anchorScroll | | Yes |
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;
}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.