Toolbars
Toolbars provide a surface for frequently used actions, typically floating or docked at the screen edge.
Toolbars are a versatile alternative to traditional App Bars in Material Design 3 Expressive. They can be docked to the bottom of the screen or float above content, providing context-aware actions in a compact, modern format.
Introduction
MD3 Expressive Toolbars are designed for high-density action surfaces. Unlike standard App Bars, which are usually pinned to the top, Toolbars are often placed at the bottom or sides of the screen to optimize thumb reachability on mobile and provide a more focused workspace on desktop. They support dynamic expansion states, toggleable action building blocks, and spatial XR elevations.
Anatomy
- Container: The background surface, available in pill (standard), large rounded, or spatial glassmorphic shapes.
- Leading Content: Actions shown only when the toolbar is expanded.
- Main Content: Primary actions that remain visible in both collapsed and expanded states.
- Trailing Content: Secondary actions or settings shown when expanded.
- FAB integration: Floating toolbars can be paired with a Floating Action Button (FAB).
Variants
Bottom Docked Toolbar
A full-width toolbar fixed at the bottom of the screen. It is the modern replacement for the traditional Bottom App Bar, supporting leading, centered, and trailing content slots.
<BottomDockedToolbar
startContent={<ToolbarIconButton aria-label="Menu" width="narrow"><Icon name="menu" /></ToolbarIconButton>}
endContent={
<div className="flex gap-2 items-center">
<ToolbarIconButton aria-label="More" emphasis="tonal"><Icon name="more_vert" /></ToolbarIconButton>
</div>
}
>
<div className="flex gap-2">
<ToolbarIconButton aria-label="Search"><Icon name="search" /></ToolbarIconButton>
<ToolbarIconButton aria-label="Edit"><Icon name="edit" /></ToolbarIconButton>
</div>
</BottomDockedToolbar>
Floating Toolbar (Horizontal & Vertical)
Floating toolbars are independent surfaces that hover over content. They are highly expressive, supporting a collapsed "pill" state and an expanded state that reveals more actions.
<HorizontalFloatingToolbar expanded={expanded}>
<ToolbarIconButton aria-label="Action"><Icon name="bolt" /></ToolbarIconButton>
</HorizontalFloatingToolbar>
Floating Toolbar with FAB
A specialized variant that docks a Floating Action Button next to a floating toolbar. This creates a cohesive "action hub" that manages both the primary page action and secondary utility actions.
<HorizontalFloatingToolbarWithFab
expanded={expanded}
floatingActionButton={<FAB icon={<Icon name="add" />} aria-label="Add" />}
>
<ToolbarIconButton aria-label="Share"><Icon name="share" /></ToolbarIconButton>
</HorizontalFloatingToolbarWithFab>
Spatial & XR Elevations (Glassmorphism)
Toolbars support 6 elevation and color variants, including spatial XR translucent surfaces with backdrop blur (backdrop-blur-md):
standard: Low-emphasis surface container.vibrant: High-emphasis primary container.surface-high: Elevated surface container high.surface-highest: Elevated surface container highest.tertiary: Tertiary container colors.xr: Semi-transparent glassmorphic surface with backdrop blur for spatial/XR experiences.
<HorizontalFloatingToolbar variant="xr" expanded={true}>
<ToolbarToggleButton selected icon={<Icon name="view_in_ar" />}>
Spatial View
</ToolbarToggleButton>
<ToolbarIconButton aria-label="Rotate"><Icon name="3d_rotation" /></ToolbarIconButton>
</HorizontalFloatingToolbar>
Custom Background & Glassmorphic Blur
Toolbars natively support Tailwind background utilities (e.g. bg-surface-container/60, bg-white/30, bg-primary/20, bg-black/40) and flexible backdrop blur classes (backdrop-blur-sm, backdrop-blur-md, backdrop-blur-xl, backdrop-blur-2xl).
When a custom bg-* class is provided via className, the toolbar automatically yields to the utility class without requiring !important overrides. This makes it effortless to craft custom frosted glass surfaces, subtle brand tints, or transparent HUD interfaces.
// Translucent surface with ultra glass backdrop blur
<HorizontalFloatingToolbar
expanded={true}
className="bg-surface-container/60 backdrop-blur-xl border border-white/20 shadow-xl"
startContent={<ToolbarIconButton aria-label="Home"><Icon name="home" /></ToolbarIconButton>}
>
<ToolbarToggleButton selected icon={<Icon name="format_bold" />}>
Bold
</ToolbarToggleButton>
<ToolbarIconButton aria-label="Palette"><Icon name="palette" /></ToolbarIconButton>
</HorizontalFloatingToolbar>
Flexibility & Slots
When configuring a toolbar, think of it as a container with slots. Slots can be populated by icon buttons, toggle buttons, images, text fields, or any custom component.
Toggle Actions & Buttons
Toolbars provide dedicated building blocks for toggleable actions (ToolbarToggleButton & ToolbarIconButton with selected state) and slot delegation (asChild):
[!NOTE] MD3 Floating Toolbar Shape Guideline:
"Don’t use square filled icon buttons in floating toolbars. Avoid using square icon buttons in floating toolbars. Their square shape conflicts with the fully-rounded shape of the floating toolbar container. Square buttons can be used in the docked toolbar."
For this reason,ToolbarIconButtonsetsmorphRadius={false}by default to maintain an uncompromised fully-rounded circular shape when clicked or selected in floating toolbars. In docked toolbars, you can explicitly opt into morphing viamorphRadius={true}.
import { HorizontalFloatingToolbar, ToolbarDivider, ToolbarIconButton, ToolbarToggleButton } from "@bug-on/m3-expressive/navigation";
<HorizontalFloatingToolbar expanded={true}>
{/* Toggle Icon Button — retains fully-rounded shape */}
<ToolbarIconButton
aria-label="Format Bold"
selected={isBold}
onClick={() => setIsBold(!isBold)}
>
<Icon name="format_bold" />
</ToolbarIconButton>
<ToolbarDivider />
{/* Toggle Button with Text & Icon */}
<ToolbarToggleButton
selected={activeTab === 'edit'}
onClick={() => setActiveTab('edit')}
icon={<Icon name="edit" />}
emphasis="tonal"
>
Edit
</ToolbarToggleButton>
{/* Slot Delegation via asChild */}
<ToolbarIconButton aria-label="Home" asChild>
<a href="#home"><Icon name="home" /></a>
</ToolbarIconButton>
</HorizontalFloatingToolbar>
Emphasis Hierarchy & Dividers
Use ToolbarIconButton to add emphasis hierarchy and ToolbarDivider to group related actions:
import { HorizontalFloatingToolbar, ToolbarDivider, ToolbarIconButton } from "@bug-on/m3-expressive/navigation";
// Single filled (high-emphasis) action alongside standard siblings
<HorizontalFloatingToolbar expanded={true}>
<ToolbarIconButton aria-label="Bold"><Icon name="format_bold" /></ToolbarIconButton>
<ToolbarIconButton aria-label="Italic"><Icon name="format_italic" /></ToolbarIconButton>
<ToolbarDivider />
{/* One filled wide button draws the eye — avoid emphasising more than one */}
<ToolbarIconButton emphasis="filled" width="wide" aria-label="Add">
<Icon name="add" />
</ToolbarIconButton>
</HorizontalFloatingToolbar>
Video-Like Toolbar Integration
Toolbars host expressive child components. By combining ToolbarIconButton with ToolbarToggleButton inside a HorizontalFloatingToolbar along with startContent and endContent, you can create rich media or gallery navigation surfaces:
<HorizontalFloatingToolbar
colors={colors}
expanded={expanded}
itemGap={4}
className="max-w-full"
startContent={
<ToolbarIconButton aria-label="Volume" width="narrow">
<Icon name="volume_up" />
</ToolbarIconButton>
}
endContent={
<ToolbarIconButton aria-label="More options" emphasis="tonal">
<Icon name="more_vert" />
</ToolbarIconButton>
}
>
{NAV_ITEMS.map((item) => (
<ToolbarToggleButton
key={item.id}
icon={<Icon name={item.icon} fill={selectedNav === item.id ? 1 : 0} />}
selected={selectedNav === item.id}
onClick={() => setSelectedNav(item.id)}
emphasis={selectedNav === item.id ? "tonal" : "standard"}
>
{item.label}
</ToolbarToggleButton>
))}
</HorizontalFloatingToolbar>
Features
Scroll Behavior
Toolbars can be configured to respond to scrolling.
- Floating Toolbars: Use the
useFloatingToolbarScrollBehaviorhook to automatically collapse or hide. - Docked Toolbars: Use the
hideOnScrollprop to slide off-screen.
Floating Toolbar Scroll
import {
HorizontalFloatingToolbarWithFab,
ToolbarIconButton,
useFloatingToolbarScrollBehavior,
} from "@bug-on/m3-expressive/navigation";
import { useRef } from "react";
export function ScrollDemo() {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const scrollBehavior = useFloatingToolbarScrollBehavior({
scrollContainerRef,
});
return (
<div className="relative h-96 flex flex-col">
<div className="flex-1 overflow-y-auto" ref={scrollContainerRef}>
{/* Scrollable list items */}
</div>
<div className="absolute bottom-4 left-0 right-0 flex justify-center pointer-events-none">
<HorizontalFloatingToolbarWithFab
expanded={scrollBehavior.isExpanded}
scrollBehavior={scrollBehavior}
floatingActionButton={<FAB icon={<Icon name="add" />} aria-label="Add" />}
startContent={
<ToolbarIconButton aria-label="Undo" width="narrow">
<Icon name="undo" />
</ToolbarIconButton>
}
>
<ToolbarIconButton aria-label="Bold"><Icon name="format_bold" /></ToolbarIconButton>
<ToolbarIconButton aria-label="Italic"><Icon name="format_italic" /></ToolbarIconButton>
</HorizontalFloatingToolbarWithFab>
</div>
</div>
);
}
[!WARNING] No DOM onScroll binding required: The
useFloatingToolbarScrollBehaviorhook relies on Framer Motion'suseScrollinternally. Do NOT bindonScroll={scrollBehavior.onScroll}to your scroll container element — simply passscrollContainerRefto the hook options.
Docked Toolbar Scroll
import { BottomDockedToolbar, ToolbarIconButton } from "@bug-on/m3-expressive/navigation";
import { useRef } from "react";
export function DockedScrollDemo() {
const scrollContainerRef = useRef<HTMLDivElement>(null);
return (
<div className="relative h-96 flex flex-col">
<div className="flex-1 overflow-y-auto" ref={scrollContainerRef}>
{/* Scrollable content */}
</div>
<BottomDockedToolbar
className="absolute"
hideOnScroll
scrollContainerRef={scrollContainerRef}
startContent={
<ToolbarIconButton aria-label="Menu" width="narrow">
<Icon name="menu" />
</ToolbarIconButton>
}
>
<ToolbarIconButton aria-label="Home" selected>
<Icon name="home" />
</ToolbarIconButton>
</BottomDockedToolbar>
</div>
);
}
Color Configuration
Toolbars support two ways to specify colors:
variantprop (Shorthand): Choose from predefined semantic MD3 themes:"standard","vibrant","surface-high","surface-highest","tertiary", or"xr".colorsprop (Custom Object): Pass an explicitFloatingToolbarColorsobject, or use pre-configured presets:standardFloatingToolbarColorsvibrantFloatingToolbarColorssurfaceContainerHighFloatingToolbarColorssurfaceContainerHighestFloatingToolbarColorstertiaryContainerFloatingToolbarColorsxrFloatingToolbarColors
Accessibility
- Roles: All toolbars apply
role="toolbar"to ensure correct screen reader behavior. - Labels: Every interactive element within a toolbar must have an
aria-labelor visible text. - Keyboard Navigation: Toolbars support standard Tab navigation; focus remains visible through the MD3 focus ring.
- Reduced Motion: All animations (expansion, translation, color shifts) respect the
prefers-reduced-motionsetting.
API Reference
BottomDockedToolbar
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'standard' | 'vibrant' | 'surface-high' | 'surface-highest' | 'tertiary' | 'xr' | 'standard' | The color and surface elevation configuration. |
colors | FloatingToolbarColors | — | Custom color configuration object overriding variant. |
hideOnScroll | boolean | false | Whether to hide the toolbar when scrolling down. |
scrollContainerRef | RefObject<HTMLElement | null> | — | Reference to the scrollable viewport (uses window scroll if omitted). |
startContent | ReactNode | — | Content at the left/start. |
endContent | ReactNode | — | Content at the right/end. |
children | ReactNode | — | Centered content. |
paddingX | number | 16 | Horizontal padding in px. |
justify | 'between' | 'center' | 'end-weighted' | 'between' | Content distribution layout. |
shape | 'none' | 'large' | 'full' | 'none' | Container shape ('large' recommended for desktop / large screens). |
className | string | — | Custom CSS class applied to the toolbar container. Supports Tailwind bg-* and backdrop-blur-* utilities. |
style | CSSProperties | — | Inline style overrides. |
aria-label | string | — | Accessible label for the toolbar landmark. |
Floating Toolbars (HorizontalFloatingToolbar / VerticalFloatingToolbar)
| Prop | Type | Default | Description |
|---|---|---|---|
expanded | boolean | — | Required. Controls visibility of startContent and endContent. |
variant | 'standard' | 'vibrant' | 'surface-high' | 'surface-highest' | 'tertiary' | 'xr' | 'standard' | Surface elevation & color configuration. Use 'xr' for glassmorphic backdrop. |
colors | FloatingToolbarColors | — | Custom color configuration object overriding variant. |
shape | 'full' | 'large' | 'full' | 'full' for pill, 'large' for rounded rectangle. |
contentPadding | CSSProperties | string | — | Custom padding for the internal container. |
startContent | ReactNode | — | Content revealed when expanded (start / top). |
endContent | ReactNode | — | Content revealed when expanded (end / bottom). |
children | ReactNode | — | Required. Main content, always visible. |
scrollBehavior | FloatingToolbarScrollBehavior | — | Scroll controller returned by useFloatingToolbarScrollBehavior. |
itemGap | number | 4 | Gap (px) between items in the center children slot. |
childrenAlignment | 'start' | 'center' | 'end' | 'center' | Alignment (justify-content) of the center children slot. |
itemClassName | string | — | Custom CSS class applied to each child item in the toolbar slots. |
disableScrollTranslation | boolean | false | Disable automatic scroll-based movement. |
disableLayoutAnimation | boolean | false | Disable motion layout transitions. |
className | string | — | Custom CSS class applied to the toolbar container. Supports Tailwind bg-* and backdrop-blur-* utilities without collision. |
style | CSSProperties | — | Inline style overrides. |
aria-label | string | — | Accessible label for the toolbar landmark. |
Floating Toolbars With FAB (HorizontalFloatingToolbarWithFab / VerticalFloatingToolbarWithFab)
Inherits all FloatingToolbarProps plus:
| Prop | Type | Default | Description |
|---|---|---|---|
floatingActionButton | ReactNode | — | Required. FAB element to dock next to the toolbar. |
fabPosition | 'start' | 'end' | 'top' | 'bottom' | 'end' (h) / 'bottom' (v) | Placement of the docked FAB. |
fabSize | 'sm' | 'md' | 'sm' | Size token: 'sm' (56×56dp) or 'md' (80×80dp). |
containerClassName | string | — | Custom CSS class applied to the outer wrapper containing both the FAB and the toolbar. Use className to style the toolbar itself. |
animationDuration | number | 0.3 | Animation duration override in seconds. |
useFloatingToolbarScrollBehavior
Hook to orchestrate scroll-based collapsing and expansion:
const scrollBehavior = useFloatingToolbarScrollBehavior(options);
Options (UseFloatingToolbarScrollBehaviorOptions)
| Option | Type | Default | Description |
|---|---|---|---|
exitDirection | 'top' | 'bottom' | 'start' | 'end' | 'bottom' | Direction the toolbar moves when collapsing. |
collapseThreshold | number | 10 | Scroll distance (px) downward to trigger collapse. |
expandThreshold | number | 10 | Scroll distance (px) upward to trigger expansion. |
scrollContainerRef | RefObject<HTMLElement | null> | — | Target scrollable container (uses window scroll if omitted). |
Return Value (FloatingToolbarScrollBehavior)
| Property | Type | Description |
|---|---|---|
offset | number | Current fractional offset (0 = fully visible, -1 = hidden). |
isExpanded | boolean | Whether toolbar leading/trailing slots are currently visible. |
setExpanded | (expanded: boolean) => void | Programmatic control of the expanded state. |
exitDirection | 'top' | 'bottom' | 'start' | 'end' | Active exit direction. |
onScroll | (event: UIEvent<HTMLElement>) => void | Legacy no-op. Kept for backwards compatibility; Framer Motion handles events automatically. |
ToolbarIconButton
Inherits all IconButton props:
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Required. The icon element to display. |
aria-label | string | — | Required accessible label. |
emphasis | 'standard' | 'tonal' | 'filled' | 'standard' | Visual color style. Use 'filled' for the single highest-priority action. |
width | 'narrow' | 'default' | 'wide' | 'default' | Slot width ratio (MD3SlotWidth): 40 / 48 / 64px. Height is always 48px touch target. |
shape | 'round' | 'square' | 'round' | Container shape. Use 'round' for floating toolbars (per MD3 spec). |
morphRadius | boolean | IconButtonMorphRadiusConfig | false | Controls border-radius morphing. Defaults to false per MD3 Floating Toolbar guideline (preserves fully-rounded circular shape). Set to true for docked toolbars. |
selected | boolean | — | Active toggle state (applies aria-pressed). |
selectedIcon | ReactNode | — | Optional active icon shown when selected={true}. |
asChild | boolean | false | Renders as the child element (e.g., Next.js Link) while keeping toolbar button styles. |
ToolbarToggleButton
| Prop | Type | Default | Description |
|---|---|---|---|
selected | boolean | false | Active toggle state (applies aria-pressed). |
emphasis | 'standard' | 'tonal' | 'filled' | 'standard' | Visual emphasis style ('standard', 'tonal', 'filled'). |
icon | ReactNode | — | Optional leading icon. |
children | ReactNode | — | Button text label or content. |
ripple | boolean | true | Enable MD3 state layer ripple effect on click. |
asChild | boolean | false | Renders as the child element (Radix Slot). |
pressExpand | boolean | true | (Deprecated) Kept for backwards compatibility. Spring border-radius morphing is used instead. |
pressExpandRatio | number | 0.06 | (Deprecated) Kept for backwards compatibility. |
ToolbarDivider
A decorative separator that visually organizes groups of actions:
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | Orientation of the toolbar. Renders a vertical line in horizontal toolbars, and horizontal in vertical. Automatically detected from context if omitted. |
className | string | — | Additional CSS class names. |
FloatingToolbarColors
Interface for custom toolbar color overrides:
| Field | Type | Description |
|---|---|---|
toolbarContainerColor | string | Background color of the toolbar surface container. |
toolbarContentColor | string | Color of the content (icons, text) inside the toolbar. |
fabContainerColor | string | Background color of the FAB container (for FAB variants). |
fabContentColor | string | Color of the content inside the FAB. |