Focus visible
Apply an outline or inset keyboard focus across Material UI components.
Starting from v9.4, Material UI provides built-in support for visual keyboard focus indicator through CSS. The demos on this page opt out of the ripple to show only the focus visible indicator.
Usage
Set focusVisible: true on the theme to render a default focus indicator on every ButtonBase-derived component when it receives keyboard focus:
import { createTheme } from '@mui/material/styles';
const theme = createTheme({ focusVisible: true });
The default focus indicator is a two-pixel solid outline with palette.primary.main color, offset by two pixels:
Press Tab to move keyboard focus — the ring appears on focus.
Inner focus indicator
Some components, for example Tab, render the focus indicator from the inside to avoid overflow-clipped container or overlapping with other elements.
Press Tab, then use the arrow keys — the ring insets so the Tabs scroller cannot clip it.
To see the full list of components that show inner focus indicator, check out the full demo below.
Colored surface container
Components that support keyboard focus visible will show another layer of box-shadow indicator when they render within AppBar, Alert, and SnackbarContent. This comes by default when the focus visible feature is enabled, unless a custom box-shadow is provided.
Press Tab — a background-colored box-shadow renders behind the outline so the ring stays visible on the colored surface.
Customization
The focusVisible can be customized by passing a CSS object to merge with the default styles.
Changing the outline color
To customize the outline, for example changing the color, pass an object with specified outline color to the focusVisible node to merge with the default outline styles:
// Recolor only; width and offset stay at the curated 2px.
createTheme({ focusVisible: { outlineColor: '#9c27b0' } });
Use box-shadow as a second layer
A boxShadow can be additive on top of the outline. This is useful for a two-color ring (WCAG technique C40) that stays visible on any background. Material UI insets the box-shadow automatically on the inner focus indicator components, so a plain single-layer value works everywhere:
createTheme({
focusVisible: {
/* inner indicator */
outlineColor: '#F9F9F9',
outlineOffset: 0,
/* outer indicator */
boxShadow: '0 0 0 4px #193146',
},
});
Press Tab — the light outline or the dark box-shadow keeps contrast on either background.
Replace outline with box-shadow
To replace the outline entirely with a box-shadow indicator, hide the outline with outlineColor: 'transparent':
createTheme({
focusVisible: {
outlineColor: 'transparent',
boxShadow: '0 0 0 3px #1976d2',
},
});
Full focus visible demo
The complete set of components that render the focus indicator when focusVisible is enabled. Use the keyboard (Tab and arrow keys) to move focus and reveal the ring.
Button
IconButton
ButtonGroup
ToggleButton
Fab
Chip
Checkbox
Radio
Switch
Pagination
ButtonBase
AccordionSummary
Details
TableSortLabel
| Name | Size |
|---|---|
| file.txt | 12 KB |
Slider
Link
Breadcrumbs
Rating
Stepper
Tab
MenuItem
- Profile
- Settings
ListItemButton
BottomNavigation
CardActionArea
Select
Autocomplete
Caveats
Checkbox and Radio custom icons must be SVG
The Checkbox and Radio attach the focus indicator to the first <svg> element inside the component. When customizing them with the icon and checkedIcon props, the custom icon must render an <svg> element — icons rendered as other elements, such as font icons or <img>, do not receive the focus indicator.
The indicator hugs whatever box the svg renders at, so smaller replacement icons get a proportionally tighter ring with no extra tuning.
Press Tab — the ring hugs the 16px svg icons.
Component focus-visible styles are replaced by the theme
Some components indicate keyboard focus with a translucent background or overlay by default — the Chip, MenuItem, ListItemButton, AccordionSummary, PaginationItem, CardActionArea, Autocomplete options, and the Slider thumb. When focusVisible is enabled, these component focus-visible styles are removed so that the theme's indicator is the only one, consistent across all components — hover, selected, and active styles are unchanged.
Recomposing a theme with a palette change
Spreading a created theme into createTheme() while changing the palette keeps the indicator color resolved from the original palette. Re-pass focusVisible in the same call so the color re-derives from the new palette:
const base = createTheme({ focusVisible: true });
// ✅ re-pass focusVisible to re-derive the color from the new palette
createTheme({
...base,
palette: { primary: { main: '#2e7d32' } },
focusVisible: true,
});