The usePreference hook provides specialized preference management for code demo variants and transformations. It builds on useLocalStorageState with intelligent storage key generation, prefix support, and automatic optimization for single-option scenarios.
The hook remembers user preferences across sessions. Try selecting different variants and refreshing the page - your choice will be preserved.
Selected:
Your preference is saved across sessions
'use client';
import * as React from 'react';
import { usePreference } from '@mui/internal-docs-infra/usePreference';
import styles from './VariantSelector.module.css';
export function VariantSelector() {
const variants = ['contained', 'outlined', 'text'];
const [variant, setVariant] = usePreference('variant', variants, () => 'contained');
return (
<div className={styles.container}>
<div className={styles.controls}>
<div className={styles.label}>Button Variant:</div>
<div className={styles.buttonGroup}>
{variants.map((option) => (
<button
key={option}
type="button"
onClick={() => setVariant(option)}
className={variant === option ? styles.buttonSelected : styles.button}
>
{option}
</button>
))}
</div>
</div>
<div className={styles.preview}>
<p className={styles.previewText}>Selected: {variant}</p>
<p className={styles.hint}>Your preference is saved across sessions</p>
</div>
</div>
);
}
import { usePreference } from '@mui/internal-docs-infra/usePreference';
function ButtonVariantSelector() {
// For multiple variants - will persist to localStorage
const [variant, setVariant] = usePreference(
'variant',
['contained', 'outlined', 'text'], // Multiple options
() => 'contained',
);
return (
<div>
<p>Current variant: {variant}</p>
{['contained', 'outlined', 'text'].map((option) => (
<button
key={option}
onClick={() => setVariant(option)}
style={{
fontWeight: variant === option ? 'bold' : 'normal',
}}
>
{option}
</button>
))}
</div>
);
}
Code language or format selection.
import { usePreference } from '@mui/internal-docs-infra/usePreference';
function CodeLanguageToggle() {
const [language, setLanguage] = usePreference(
'transform',
['typescript', 'javascript'],
() => 'typescript',
);
return (
<div>
<label>
<input
type="radio"
checked={language === 'typescript'}
onChange={() => setLanguage('typescript')}
/>
TypeScript
</label>
<label>
<input
type="radio"
checked={language === 'javascript'}
onChange={() => setLanguage('javascript')}
/>
JavaScript
</label>
</div>
);
}
import { usePreference, PreferencesProvider } from '@mui/internal-docs-infra/usePreference';
function CustomPrefixDemo() {
return (
<PreferencesProvider value={{ prefix: 'my-app' }}>
<ComponentWithPreferences />
</PreferencesProvider>
);
}
}
function ComponentWithPreferences() {
// Storage key will be: "my-app_variant:contained:outlined:text"
const [variant, setVariant] = usePreference(
'variant',
['contained', 'outlined', 'text'],
() => 'contained',
);
return (
<select value={variant || 'contained'} onChange={(e) => setVariant(e.target.value)}>
<option value="contained">Contained</option>
<option value="outlined">Outlined</option>
<option value="text">Text</option>
</select>
);
}
Manage multiple independent preferences in a single component.
import { usePreference } from '@mui/internal-docs-infra/usePreference';
function DemoConfigPanel() {
const [size, setSize] = usePreference('variant', ['small', 'medium', 'large'], () => 'medium');
const [color, setColor] = usePreference(
'variant',
['primary', 'secondary', 'error'],
() => 'primary',
);
const [format, setFormat] = usePreference(
'transform',
['typescript', 'javascript'],
() => 'typescript',
);
return (
<div>
<select value={size || 'medium'} onChange={(e) => setSize(e.target.value)}>
<option>small</option>
<option>medium</option>
<option>large</option>
</select>
<select value={color || 'primary'} onChange={(e) => setColor(e.target.value)}>
<option>primary</option>
<option>secondary</option>
<option>error</option>
</select>
<select value={format || 'typescript'} onChange={(e) => setFormat(e.target.value)}>
<option>typescript</option>
<option>javascript</option>
</select>
</div>
);
}
const [preference, setPreference] = usePreference(type, name, initializer);
| Parameter | Type | Description |
|---|---|---|
type | 'variant' | 'transform' | Type of preference (affects storage prefix) |
name | string | string[] | Variant/transform name(s). Array gets sorted and joined |
initializer | string | null | (() => string | null) | Initial value or function |
| Property | Type | Description |
|---|---|---|
preference | string | null | Current preference value |
setPreference | React.Dispatch<React.SetStateAction<string | null>> | Function to update preference |
function usePreference(
type: 'variant' | 'transform',
name: string | string[],
initializer?: string | null | (() => string | null),
): [string | null, React.Dispatch<React.SetStateAction<string | null>>];
interface PreferencesContext {
prefix?: string;
}
The hook automatically skips localStorage for single-option scenarios:
### Demo Configuration Panel
```tsx
import { usePreference } from '@mui/internal-docs-infra/usePreference';
function DemoConfigPanel() {
const [size, setSize] = usePreference('variant', ['small', 'medium', 'large'], () => 'medium');
const [color, setColor] = usePreference(
'variant',
['primary', 'secondary', 'error'],
() => 'primary',
);
const [format, setFormat] = usePreference(
'transform',
['typescript', 'javascript'],
() => 'typescript',
);
return (
<div className="demo-config">
<h4>Demo Configuration</h4>
<label>
Size:
<select value={size || 'medium'} onChange={(e) => setSize(e.target.value)}>
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</label>
<label>
Color:
<select value={color || 'primary'} onChange={(e) => setColor(e.target.value)}>
<option value="primary">Primary</option>
<option value="secondary">Secondary</option>
<option value="error">Error</option>
</select>
</label>
<label>
Code Format:
<select value={format || 'typescript'} onChange={(e) => setFormat(e.target.value)}>
<option value="typescript">TypeScript</option>
<option value="javascript">JavaScript</option>
</select>
</label>
</div>
);
}
The hook generates localStorage keys based on the type and name parameters:
// Single option - no localStorage needed
usePreference('variant', ['contained']);
// → Storage key: null (behaves like useState)
// Multiple options - creates stable key
usePreference('variant', ['contained', 'outlined', 'text']);
// → Storage key: "_docs_variant_pref:contained:outlined:text"
Keys are generated based on type and name:
const key = React.useMemo(() => {
if (!Array.isArray(name)) {
return name; // Single string always persists
}
if (name.length <= 1) {
return null; // Single option - no need to persist choice
}
return [...name].sort().join(':'); // Multiple options - create stable key
}, [name]);
Default prefixes can be overridden via context:
// Default: "_docs_variant_pref" or "_docs_transform_pref"
// With custom prefix: "{prefix}_variant" or "{prefix}_transform"
When NOT to use:
useLocalStorageState directly for general preferencesuseLocalStorageState - Underlying persistence mechanismPreferencesProvider - Context provider for custom prefixesCodeHighlighter - Common use case for variant preferences