Building Interactive Menus with React MUI Speed Dial: A Complete Guide
As developers, we often need to create compact interfaces that provide quick access to multiple actions without cluttering the UI. The MUI Speed Dial component offers an elegant solution to this challenge, providing a floating action button that expands to reveal a set of related actions. I've implemented this component in numerous projects, and it's particularly effective for mobile-first applications where screen real estate is limited.
In this guide, I'll walk you through everything you need to know about the MUI Speed Dial component - from basic implementation to advanced customizations. By the end, you'll be able to create intuitive action menus that enhance your application's user experience while maintaining a clean interface.
Learning Objectives
After reading this article, you'll be able to:
- Understand the core functionality and purpose of the MUI Speed Dial component
- Implement basic and advanced Speed Dial configurations in your React applications
- Customize the appearance and behavior of Speed Dial to match your design requirements
- Handle user interactions and state management with Speed Dial
- Integrate Speed Dial with other MUI components for cohesive UI solutions
- Apply accessibility best practices to ensure your Speed Dial is usable by everyone
Understanding the MUI Speed Dial Component
The Speed Dial component is inspired by Google's Material Design and functions as an expandable floating action button (FAB) that reveals related actions when clicked. It's essentially a compact menu that remains hidden until needed, making it perfect for secondary actions that don't need to be constantly visible.
Speed Dial consists of two main parts: the main action button and a set of action items that appear when the main button is activated. This pattern helps maintain a clean interface while still providing quick access to functionality when needed.
Core Concepts
The Speed Dial follows a few key principles:
- Progressive disclosure - It reveals options only when needed, reducing cognitive load
- Contextual actions - It groups related actions together
- Space efficiency - It conserves screen space by hiding secondary actions
- Quick access - It provides immediate access to important functions
This component is particularly useful in mobile applications or any interface where space is limited, but it can be equally effective in desktop applications for grouping related actions.
Speed Dial Component API Deep Dive
Before diving into implementation, let's explore the component's API to understand its capabilities and configuration options.
Key Props
The Speed Dial component offers numerous props to customize its behavior and appearance. Here are the most important ones:
Prop | Type | Default | Description |
---|---|---|---|
ariaLabel | string | - | Required. Aria label for the SpeedDial component. |
open | boolean | false | If true, the component is shown in an open state. |
icon | node | <SpeedDialIcon /> | The icon to display in the SpeedDial Floating Action Button. |
direction | 'up' | 'down' | 'left' | 'right' | 'up' | Direction the actions should open. |
onClose | func | - | Callback fired when the component requests to be closed. |
onOpen | func | - | Callback fired when the component requests to be open. |
FabProps | object | `` | Props applied to the Fab component. |
hidden | boolean | false | If true, the SpeedDial will be hidden. |
openIcon | node | - | The icon to display in the SpeedDial Fab when the SpeedDial is open. |
TransitionComponent | elementType | Zoom | The component used for the transition. |
transitionDuration | number | appear?: number, enter?: number, exit?: number | - | The duration for the transition, in milliseconds. |
children | node | - | SpeedDialAction components to display when the SpeedDial is open. |
SpeedDialAction Props
The SpeedDial component works together with SpeedDialAction components, which represent individual actions in the menu. Here are the key props for SpeedDialAction:
Prop | Type | Default | Description |
---|---|---|---|
icon | node | - | Required. The icon to display in the SpeedDial Fab. |
tooltipTitle | node | - | Label to display in the tooltip. |
tooltipOpen | boolean | false | If true, the tooltip is always shown. |
FabProps | object | Props applied to the Fab component. | |
TooltipClasses | object | Classes applied to the Tooltip component. | |
onClick | func | - | Callback fired when the action item is clicked. |
Controlled vs Uncontrolled Usage
Like many React components, Speed Dial can be used in both controlled and uncontrolled modes:
Controlled Mode: You manage the open state externally and pass it to the component along with handlers for open and close events. This gives you complete control over when the Speed Dial opens and closes.
Uncontrolled Mode: The component manages its own state internally. You can still define how it responds to interactions through props like onClose
and onOpen
.
I generally recommend using the controlled approach for most applications, as it gives you more flexibility and makes it easier to coordinate the Speed Dial state with other parts of your application.
Getting Started with Speed Dial
Let's start by implementing a basic Speed Dial component in a React application.
Installation
First, make sure you have the required packages installed:
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
Basic Implementation
Here's a simple implementation of a Speed Dial component with three actions:
import React, { useState } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const BasicSpeedDial = () => {
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="SpeedDial basic example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => console.log(`Clicked on ${action.name}`)}
/>
))}
</SpeedDial>
</Box>
);
};
export default BasicSpeedDial;
In this example:
- We import the necessary components from MUI
- We define an array of actions, each with an icon and a name
- We render a Speed Dial component with the actions mapped to SpeedDialAction components
- We position the Speed Dial at the bottom right of its container
- Each action displays a tooltip with its name when hovered
The result is a Speed Dial button that, when clicked, reveals four action buttons with tooltips.
Creating a Controlled Speed Dial
For more control over the component's behavior, let's implement a controlled version:
import React, { useState } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
import EditIcon from '@mui/icons-material/Edit';
const ControlledSpeedDial = () => {
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="SpeedDial controlled example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon openIcon={<EditIcon />} />}
onClose={handleClose}
onOpen={handleOpen}
open={open} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => {
console.log(`Clicked on ${action.name}`);
handleClose();
}}
/>
))}
</SpeedDial>
</Box>
);
};
export default ControlledSpeedDial;
In this controlled version:
- We use React's
useState
hook to manage the open state - We provide
handleOpen
andhandleClose
functions to control the state - We pass the
open
state and handlers to the SpeedDial component - We specify a custom
openIcon
(EditIcon) that appears when the Speed Dial is open - We close the Speed Dial after an action is clicked
This approach gives you full control over when the Speed Dial opens and closes, allowing you to coordinate it with other UI elements or application state.
Customizing Speed Dial Direction
One of the most useful features of Speed Dial is the ability to control the direction in which the actions appear. This is particularly helpful when positioning the Speed Dial in different areas of your interface.
import React, { useState } from 'react';
import { Box, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, SpeedDial, SpeedDialAction, SpeedDialIcon } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const DirectionalSpeedDial = () => {
const [direction, setDirection] = useState('up');
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
const handleDirectionChange = (event) => {
setDirection(event.target.value);
};
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<FormControl component="fieldset" sx={{ marginBottom: 2 }}>
<FormLabel component="legend">Direction</FormLabel>
<RadioGroup
row
name="direction"
value={direction}
onChange={handleDirectionChange}
>
<FormControlLabel value="up" control={<Radio />} label="Up" />
<FormControlLabel value="right" control={<Radio />} label="Right" />
<FormControlLabel value="down" control={<Radio />} label="Down" />
<FormControlLabel value="left" control={<Radio />} label="Left" />
</RadioGroup>
</FormControl>
<SpeedDial
ariaLabel="SpeedDial directional example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
direction={direction}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => console.log(`Clicked on ${action.name}`)}
/>
))}
</SpeedDial>
</Box>
);
};
export default DirectionalSpeedDial;
In this example:
- We add a radio button group to control the direction
- We use React state to track the selected direction
- We pass the direction value to the SpeedDial component
This allows users to see how the Speed Dial behaves in different directions, and you can choose the direction that best fits your layout.
Creating a Custom Trigger for Speed Dial
By default, the Speed Dial opens on click, but you can customize this behavior using the onOpen
and onClose
props. Let's create a Speed Dial that opens on hover:
import React, { useState } from 'react';
import { Box, SpeedDial, SpeedDialAction, SpeedDialIcon } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const HoverSpeedDial = () => {
const [open, setOpen] = useState(false);
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="SpeedDial hover example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
FabProps={{
onMouseEnter: () => setOpen(true),
onMouseLeave: () => setOpen(false),
}} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => console.log(`Clicked on ${action.name}`)}
FabProps={{
onMouseEnter: () => setOpen(true),
onMouseLeave: () => setOpen(false),
}}
/>
))}
</SpeedDial>
</Box>
);
};
export default HoverSpeedDial;
In this example:
- We use the
FabProps
prop to add mouse event handlers to the main button - We also add the same handlers to each action button
- The Speed Dial opens when the mouse enters either the main button or any of the action buttons
- It closes when the mouse leaves both
This creates a more interactive experience where users can quickly access actions by hovering, without requiring a click.
Styling and Customizing Speed Dial
The Speed Dial component is highly customizable. Let's explore various ways to style it to match your application's design.
Customizing with the sx Prop
The sx
prop is the most direct way to customize MUI components:
import React from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const StyledSpeedDial = () => {
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Styled SpeedDial example"
sx={{
position: 'absolute',
bottom: 16,
right: 16,
'& .MuiFab-primary': {
backgroundColor: '#6200ea',
'&:hover': {
backgroundColor: '#3700b3',
},
width: 60,
height: 60,
}
}}
icon={<SpeedDialIcon />} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
sx={{
'& .MuiFab-primary': {
backgroundColor: '#bb86fc',
'&:hover': {
backgroundColor: '#6200ea',
},
}
}}
FabProps={{
size: 'small',
}}
onClick={() => console.log(`Clicked on ${action.name}`)}
/>
))}
</SpeedDial>
</Box>
);
};
export default StyledSpeedDial;
In this example:
- We use the
sx
prop to customize the main SpeedDial button, changing its background color and size - We also customize each SpeedDialAction button with a different color scheme
- We use the
FabProps
prop to set the size of the action buttons to "small"
Customizing with Theme Overrides
For more global styling, you can override the default theme:
import React from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction, createTheme, ThemeProvider } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const ThemedSpeedDial = () => {
const theme = createTheme({
components: {
MuiSpeedDial: {
styleOverrides: {
root: {
'& .MuiSpeedDial-fab': {
backgroundColor: '#03dac6',
'&:hover': {
backgroundColor: '#018786',
},
},
},
},
},
MuiSpeedDialAction: {
styleOverrides: {
fab: {
backgroundColor: '#e0f7fa',
'&:hover': {
backgroundColor: '#b2ebf2',
},
color: '#006064',
},
},
},
},
});
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<ThemeProvider theme={theme}>
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Themed SpeedDial example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => console.log(`Clicked on ${action.name}`)}
/>
))}
</SpeedDial>
</Box>
</ThemeProvider>
);
};
export default ThemedSpeedDial;
In this example:
- We create a custom theme with overrides for the SpeedDial and SpeedDialAction components
- We wrap our component in a ThemeProvider with our custom theme
- The styling is applied to all SpeedDial components within the ThemeProvider
This approach is particularly useful when you want to maintain consistent styling across multiple SpeedDial instances in your application.
Advanced Speed Dial Patterns
Now that we've covered the basics, let's explore some more advanced usage patterns for the Speed Dial component.
Speed Dial with Custom Icons
You can customize both the closed and open states of the Speed Dial icon:
import React, { useState } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
import AddIcon from '@mui/icons-material/Add';
import CloseIcon from '@mui/icons-material/Close';
const CustomIconSpeedDial = () => {
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Custom icon SpeedDial"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={
<SpeedDialIcon
icon={<AddIcon />}
openIcon={<CloseIcon />}
/>
}
onClose={handleClose}
onOpen={handleOpen}
open={open} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => {
console.log(`Clicked on ${action.name}`);
handleClose();
}}
/>
))}
</SpeedDial>
</Box>
);
};
export default CustomIconSpeedDial;
In this example:
- We use a custom
AddIcon
for the closed state - We use a
CloseIcon
for the open state - This provides a clearer visual indication of the component's state
Persistent Tooltips
By default, tooltips only appear on hover. You can make them always visible:
import React from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const PersistentTooltipsSpeedDial = () => {
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Persistent tooltips SpeedDial"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
tooltipOpen
onClick={() => console.log(`Clicked on ${action.name}`)}
/>
))}
</SpeedDial>
</Box>
);
};
export default PersistentTooltipsSpeedDial;
In this example:
- We set
tooltipOpen={true}
on each SpeedDialAction - This makes the tooltips always visible when the Speed Dial is open
- This is useful for making actions more discoverable, especially for new users
Integrating with Other Components
Speed Dial can be integrated with other MUI components for more complex interactions:
import React, { useState } from 'react';
import {
Box,
SpeedDial,
SpeedDialIcon,
SpeedDialAction,
Snackbar,
Alert
} from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const IntegratedSpeedDial = () => {
const [open, setOpen] = useState(false);
const [snackbar, setSnackbar] = useState({
open: false,
message: '',
severity: 'success'
});
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
const handleActionClick = (actionName) => {
setOpen(false);
// Simulate different outcomes based on the action
if (actionName === 'Save') {
setSnackbar({
open: true,
message: 'Document saved successfully!',
severity: 'success'
});
} else if (actionName === 'Print') {
setSnackbar({
open: true,
message: 'Sending to printer...',
severity: 'info'
});
} else if (actionName === 'Share') {
setSnackbar({
open: true,
message: 'Sharing options opened',
severity: 'info'
});
} else if (actionName === 'Copy') {
setSnackbar({
open: true,
message: 'Content copied to clipboard',
severity: 'success'
});
}
};
const handleSnackbarClose = () => {
setSnackbar({
...snackbar,
open: false
});
};
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Integrated SpeedDial example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => handleActionClick(action.name)}
/>
))}
</SpeedDial>
<Snackbar
open={snackbar.open}
autoHideDuration={4000}
onClose={handleSnackbarClose}
>
<Alert
onClose={handleSnackbarClose}
severity={snackbar.severity}
>
{snackbar.message}
</Alert>
</Snackbar>
</Box>
);
};
export default IntegratedSpeedDial;
In this example:
- We integrate the Speed Dial with Snackbar and Alert components
- Each action triggers a different type of notification
- This demonstrates how Speed Dial can be part of a more complex interaction flow
Building a Real-World Speed Dial: Document Actions Example
Let's build a more practical example: a document editor with a Speed Dial for common document actions.
import React, { useState } from 'react';
import {
Box,
Paper,
Typography,
SpeedDial,
SpeedDialIcon,
SpeedDialAction,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
Snackbar,
Alert
} from '@mui/material';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
import FormatPaintIcon from '@mui/icons-material/FormatPaint';
import EditIcon from '@mui/icons-material/Edit';
const DocumentEditorWithSpeedDial = () => {
const [open, setOpen] = useState(false);
const [shareDialogOpen, setShareDialogOpen] = useState(false);
const [email, setEmail] = useState('');
const [snackbar, setSnackbar] = useState({
open: false,
message: '',
severity: 'success'
});
const actions = [
{ icon: <SaveIcon />, name: 'Save', color: '#4caf50' },
{ icon: <PrintIcon />, name: 'Print', color: '#2196f3' },
{ icon: <ShareIcon />, name: 'Share', color: '#ff9800' },
{ icon: <PictureAsPdfIcon />, name: 'Export as PDF', color: '#f44336' },
{ icon: <FormatPaintIcon />, name: 'Format', color: '#9c27b0' }
];
const handleActionClick = (actionName) => {
setOpen(false);
switch(actionName) {
case 'Save':
setSnackbar({
open: true,
message: 'Document saved successfully!',
severity: 'success'
});
break;
case 'Print':
window.print();
setSnackbar({
open: true,
message: 'Print dialog opened',
severity: 'info'
});
break;
case 'Share':
setShareDialogOpen(true);
break;
case 'Export as PDF':
setSnackbar({
open: true,
message: 'PDF export started',
severity: 'info'
});
break;
case 'Format':
setSnackbar({
open: true,
message: 'Format options opened',
severity: 'info'
});
break;
default:
break;
}
};
const handleShareSubmit = () => {
setShareDialogOpen(false);
setSnackbar({
open: true,
message: `Document shared with ${email}`,
severity: 'success'
});
setEmail('');
};
const handleSnackbarClose = () => {
setSnackbar({
...snackbar,
open: false
});
};
return (
<Box sx={{ position: 'relative', height: '100vh', p: 2 }}>
<Paper
elevation={3}
sx={{
p: 3,
minHeight: '80vh',
maxWidth: 800,
mx: 'auto'
}} >
<Typography variant="h4" gutterBottom>
Document Title
</Typography>
<Typography paragraph>
This is a sample document. You can use the Speed Dial in the bottom right corner to perform actions on this document.
</Typography>
<Typography paragraph>
Try clicking on the Speed Dial button to see the available actions. Each action will trigger a different response.
</Typography>
</Paper>
<SpeedDial
ariaLabel="Document actions"
sx={{ position: 'fixed', bottom: 16, right: 16 }}
icon={<SpeedDialIcon openIcon={<EditIcon />} />}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
FabProps={{
sx: {
bgcolor: action.color,
'&:hover': {
bgcolor: action.color,
opacity: 0.9
}
}
}}
onClick={() => handleActionClick(action.name)}
/>
))}
</SpeedDial>
<Dialog open={shareDialogOpen} onClose={() => setShareDialogOpen(false)}>
<DialogTitle>Share Document</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="email"
label="Email Address"
type="email"
fullWidth
variant="outlined"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setShareDialogOpen(false)}>Cancel</Button>
<Button onClick={handleShareSubmit} variant="contained">Share</Button>
</DialogActions>
</Dialog>
<Snackbar
open={snackbar.open}
autoHideDuration={4000}
onClose={handleSnackbarClose}
>
<Alert
onClose={handleSnackbarClose}
severity={snackbar.severity}
>
{snackbar.message}
</Alert>
</Snackbar>
</Box>
);
};
export default DocumentEditorWithSpeedDial;
In this comprehensive example:
- We create a document editor interface with a SpeedDial for common actions
- Each action has a unique color for visual differentiation
- We implement different behaviors for each action:
- Save: Shows a success message
- Print: Opens the browser print dialog
- Share: Opens a dialog to enter an email address
- Export as PDF: Shows an info message
- Format: Shows an info message
- We use a Dialog component for the share functionality
- We use Snackbar and Alert components to provide feedback
This example demonstrates how Speed Dial can be integrated into a real application to provide quick access to contextual actions.
Accessibility Considerations
Accessibility is crucial for ensuring your Speed Dial can be used by everyone. Here are some key considerations:
Keyboard Navigation
import React, { useState, useEffect, useRef } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const AccessibleSpeedDial = () => {
const [open, setOpen] = useState(false);
const [focusedAction, setFocusedAction] = useState(-1);
const speedDialRef = useRef(null);
const actionRefs = useRef([]);
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' }
];
// Reset focused action when closing
useEffect(() => {
if (!open) {
setFocusedAction(-1);
}
}, [open]);
const handleKeyDown = (event) => {
if (!open) {
if (event.key === 'Enter' || event.key === ' ') {
setOpen(true);
event.preventDefault();
}
} else {
switch (event.key) {
case 'Escape':
setOpen(false);
speedDialRef.current?.focus();
event.preventDefault();
break;
case 'ArrowUp':
setFocusedAction(prev => Math.max(prev - 1, 0));
event.preventDefault();
break;
case 'ArrowDown':
setFocusedAction(prev => Math.min(prev + 1, actions.length - 1));
event.preventDefault();
break;
case 'Enter':
case ' ':
if (focusedAction >= 0) {
console.log(`Activated: ${actions[focusedAction].name}`);
setOpen(false);
event.preventDefault();
}
break;
default:
break;
}
}
};
// Focus the action when focusedAction changes
useEffect(() => {
if (focusedAction >= 0 && actionRefs.current[focusedAction]) {
actionRefs.current[focusedAction].focus();
}
}, [focusedAction]);
return (
<Box
sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}
onKeyDown={handleKeyDown} >
<SpeedDial
ariaLabel="Accessible SpeedDial"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
ref={speedDialRef} >
{actions.map((action, index) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => {
console.log(`Clicked on ${action.name}`);
setOpen(false);
}}
ref={el => {
actionRefs.current[index] = el;
}}
aria-label={action.name}
/>
))}
</SpeedDial>
</Box>
);
};
export default AccessibleSpeedDial;
In this example:
- We implement keyboard navigation for the Speed Dial
- Users can press Enter or Space to open the Speed Dial
- When open, users can use arrow keys to navigate between actions
- Enter or Space activates the selected action
- Escape closes the Speed Dial
- We maintain focus management to ensure keyboard users can navigate effectively
ARIA Attributes
The MUI Speed Dial component includes built-in ARIA attributes, but we can enhance them further:
import React, { useState } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
const AriaEnhancedSpeedDial = () => {
const [open, setOpen] = useState(false);
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy', description: 'Copy document to clipboard' },
{ icon: <SaveIcon />, name: 'Save', description: 'Save document to your device' },
{ icon: <PrintIcon />, name: 'Print', description: 'Print document' },
{ icon: <ShareIcon />, name: 'Share', description: 'Share document with others' }
];
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Document actions menu"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
FabProps={{
'aria-expanded': open,
'aria-haspopup': true,
'aria-controls': 'speed-dial-actions'
}} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => {
console.log(`Clicked on ${action.name}`);
setOpen(false);
}}
aria-label={action.description}
/>
))}
</SpeedDial>
</Box>
);
};
export default AriaEnhancedSpeedDial;
In this example:
- We provide a more descriptive
ariaLabel
for the SpeedDial - We add
aria-expanded
,aria-haspopup
, andaria-controls
attributes to the main button - We use more descriptive
aria-label
attributes for each action - These enhancements help screen reader users understand the component's purpose and state
Common Issues and Solutions
When working with the Speed Dial component, you might encounter some common issues. Here's how to address them:
Issue 1: Speed Dial Actions Not Visible
If your Speed Dial actions aren't visible when the Speed Dial is open, it might be due to container overflow issues.
Solution:
<Box sx={{
height: 320,
transform: 'translateZ(0px)', // Creates a stacking context
flexGrow: 1,
position: 'relative',
overflow: 'visible' // Important!
}}>
<SpeedDial
ariaLabel="SpeedDial example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
>
{/* Actions here */}
</SpeedDial>
</Box>
The key is to ensure:
- The container has
overflow: visible
- The container has
position: relative
- The Speed Dial has
position: absolute
Issue 2: Z-Index Conflicts
Sometimes the Speed Dial might appear behind other elements.
Solution:
<SpeedDial
ariaLabel="SpeedDial example"
sx={{
position: 'absolute',
bottom: 16,
right: 16,
zIndex: 1000 // Increase z-index
}}
icon={<SpeedDialIcon />}
>
{/* Actions here */}
</SpeedDial>
Increase the zIndex
value to ensure the Speed Dial appears above other elements.
Issue 3: Speed Dial Closing Immediately After Opening
This can happen when click events propagate to parent elements.
Solution:
import React, { useState } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
const FixedSpeedDial = () => {
const [open, setOpen] = useState(false);
const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' }
];
const handleSpeedDialClick = (event) => {
// Prevent event propagation
event.stopPropagation();
setOpen(!open);
};
return (
<Box
sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}
onClick={() => setOpen(false)} // Close when clicking outside >
<SpeedDial
ariaLabel="Fixed SpeedDial example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
onClick={handleSpeedDialClick}
open={open} >
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={(event) => {
event.stopPropagation();
console.log(`Clicked on ${action.name}`);
setOpen(false);
}}
/>
))}
</SpeedDial>
</Box>
);
};
export default FixedSpeedDial;
In this example:
- We use
event.stopPropagation()
to prevent click events from bubbling up - We handle the open/close state manually with our own click handler
Issue 4: Performance with Many Actions
If you have many actions, you might notice performance issues.
Solution:
import React, { useState, useMemo } from 'react';
import { Box, SpeedDial, SpeedDialIcon, SpeedDialAction } from '@mui/material';
import FileCopyIcon from '@mui/icons-material/FileCopyOutlined';
import SaveIcon from '@mui/icons-material/Save';
import PrintIcon from '@mui/icons-material/Print';
import ShareIcon from '@mui/icons-material/Share';
// Import more icons as needed
const OptimizedSpeedDial = () => {
const [open, setOpen] = useState(false);
// Define actions outside of render
const actions = useMemo(() => [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' },
// Add more actions as needed
], []);
// Only render actions when open
const renderActions = open ? actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => {
console.log(`Clicked on ${action.name}`);
setOpen(false);
}}
/>
)) : null;
return (
<Box sx={{ height: 320, transform: 'translateZ(0px)', flexGrow: 1 }}>
<SpeedDial
ariaLabel="Optimized SpeedDial example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<SpeedDialIcon />}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open} >
{renderActions}
</SpeedDial>
</Box>
);
};
export default OptimizedSpeedDial;
In this example:
- We use
useMemo
to prevent recreating the actions array on each render - We only render the action components when the Speed Dial is open
- This can significantly improve performance with many actions
Best Practices for Using Speed Dial
Based on my experience working with the MUI Speed Dial component, here are some best practices to follow:
1. Keep Actions Focused and Limited
Limit the number of actions to 5-6 at most. The Speed Dial is meant for quick access to common actions, not as a comprehensive menu.
2. Use Clear Icons
Choose icons that clearly represent the action they perform. If the meaning isn't immediately obvious, use tooltips to clarify.
3. Group Related Actions
Only group related actions in a single Speed Dial. If you have distinct groups of actions, consider using multiple Speed Dials or a different UI pattern.
4. Position Thoughtfully
Position the Speed Dial where it's accessible but doesn't obscure important content. Common positions are bottom-right, bottom-center, or top-right.
5. Provide Visual Feedback
Give users clear feedback when they interact with the Speed Dial. This could be through animations, color changes, or notifications.
6. Make It Discoverable
Ensure users can discover the Speed Dial. Consider adding a brief tutorial or hint for first-time users.
7. Test on Mobile
Since Speed Dial is particularly useful on mobile devices, thoroughly test its behavior and accessibility on various mobile devices and screen sizes.
8. Optimize for Touch
Make sure the action buttons are large enough for comfortable touch interaction (at least 48x48 pixels).
9. Consider the Direction
Choose the opening direction based on the Speed Dial's position on the screen. For example, if it's at the bottom of the screen, open upward.
10. Enhance Accessibility
Always provide proper ARIA attributes and ensure keyboard navigation works correctly.
Wrapping Up
The MUI Speed Dial component is a powerful tool for creating compact, accessible action menus in your React applications. We've explored its basic usage, customization options, advanced patterns, and best practices.
By following the guidelines in this article, you can implement Speed Dials that enhance your application's user experience by providing quick access to important actions without cluttering the interface. Remember to keep your Speed Dials focused, accessible, and visually consistent with your overall design.
Whether you're building a document editor, a social media app, or an admin dashboard, the Speed Dial component can help you create a more intuitive and efficient user interface. Experiment with different configurations and customizations to find the approach that works best for your specific use case.