Material-UI 导航组件 #
概述 #
MUI 提供了完整的导航组件,帮助构建清晰的应用导航结构。
text
导航组件体系
│
├── AppBar 应用栏
├── Drawer 抽屉导航
├── Tabs 标签页
├── Menu 下拉菜单
├── Breadcrumbs 面包屑
├── BottomNavigation 底部导航
└── Stepper 步骤器
AppBar 应用栏 #
基础用法 #
jsx
import { AppBar, Toolbar, Typography, Button, IconButton } from '@mui/material';
import { Menu as MenuIcon } from '@mui/icons-material';
<AppBar position="static">
<Toolbar>
<IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
应用名称
</Typography>
<Button color="inherit">登录</Button>
</Toolbar>
</AppBar>
位置 #
jsx
<Stack spacing={2} sx={{ mb: 2 }}>
<AppBar position="fixed">
<Toolbar><Typography>Fixed</Typography></Toolbar>
</AppBar>
<AppBar position="absolute">
<Toolbar><Typography>Absolute</Typography></Toolbar>
</AppBar>
<AppBar position="sticky">
<Toolbar><Typography>Sticky</Typography></Toolbar>
</AppBar>
<AppBar position="static">
<Toolbar><Typography>Static</Typography></Toolbar>
</AppBar>
</Stack>
颜色 #
jsx
<Stack spacing={2}>
<AppBar position="static" color="primary">
<Toolbar><Typography>Primary</Typography></Toolbar>
</AppBar>
<AppBar position="static" color="secondary">
<Toolbar><Typography>Secondary</Typography></Toolbar>
</AppBar>
<AppBar position="static" color="inherit">
<Toolbar><Typography>Inherit</Typography></Toolbar>
</AppBar>
<AppBar position="static" color="transparent">
<Toolbar><Typography>Transparent</Typography></Toolbar>
</AppBar>
</Stack>
带搜索框 #
jsx
import { Search, SearchIconWrapper, StyledInputBase } from './SearchComponents';
import { Search as SearchIcon } from '@mui/icons-material';
<AppBar position="static">
<Toolbar>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1, display: { xs: 'none', sm: 'block' } }}>
MUI
</Typography>
<Search>
<SearchIconWrapper>
<SearchIcon />
</SearchIconWrapper>
<StyledInputBase placeholder="搜索..." inputProps={{ 'aria-label': 'search' }} />
</Search>
</Toolbar>
</AppBar>
响应式菜单 #
jsx
import { MenuItem, Menu } from '@mui/material';
function ResponsiveAppBar() {
const [anchorElNav, setAnchorElNav] = useState(null);
const pages = ['产品', '服务', '关于我们'];
const handleOpenNavMenu = (event) => setAnchorElNav(event.currentTarget);
const handleCloseNavMenu = () => setAnchorElNav(null);
return (
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
<Typography variant="h6" noWrap component="a" href="/" sx={{ mr: 2, display: { xs: 'none', md: 'flex' } }}>
LOGO
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton size="large" onClick={handleOpenNavMenu} color="inherit">
<MenuIcon />
</IconButton>
<Menu
anchorEl={anchorElNav}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
keepMounted
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
sx={{ display: { xs: 'block', md: 'none' } }}
>
{pages.map((page) => (
<MenuItem key={page} onClick={handleCloseNavMenu}>
<Typography textAlign="center">{page}</Typography>
</MenuItem>
))}
</Menu>
</Box>
<Typography variant="h5" noWrap component="a" href="/" sx={{ mr: 2, display: { xs: 'flex', md: 'none' }, flexGrow: 1 }}>
LOGO
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{pages.map((page) => (
<Button key={page} onClick={handleCloseNavMenu} sx={{ my: 2, color: 'white', display: 'block' }}>
{page}
</Button>
))}
</Box>
</Toolbar>
</Container>
</AppBar>
);
}
Drawer 抽屉导航 #
基础用法 #
jsx
import { Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { Inbox as InboxIcon, Mail as MailIcon } from '@mui/icons-material';
function TemporaryDrawer() {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>打开抽屉</Button>
<Drawer open={open} onClose={() => setOpen(false)}>
<Box sx={{ width: 250 }} role="presentation" onClick={() => setOpen(false)}>
<List>
{['收件箱', '星标', '发送邮件', '草稿'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
</Drawer>
</>
);
}
锚点位置 #
jsx
<Drawer anchor="left" open={open} onClose={() => setOpen(false)}>
左侧抽屉
</Drawer>
<Drawer anchor="right" open={open} onClose={() => setOpen(false)}>
右侧抽屉
</Drawer>
<Drawer anchor="top" open={open} onClose={() => setOpen(false)}>
顶部抽屉
</Drawer>
<Drawer anchor="bottom" open={open} onClose={() => setOpen(false)}>
底部抽屉
</Drawer>
永久抽屉 #
jsx
const drawerWidth = 240;
function PermanentDrawer() {
return (
<Box sx={{ display: 'flex' }}>
<AppBar position="fixed" sx={{ zIndex: 1201 }}>
<Toolbar>
<Typography variant="h6" noWrap>
永久抽屉
</Typography>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box' },
}}
>
<Toolbar />
<Box sx={{ overflow: 'auto' }}>
<List>
{['首页', '用户', '设置'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
</Drawer>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Toolbar />
<Typography>主内容区域</Typography>
</Box>
</Box>
);
}
可折叠抽屉 #
jsx
function MiniDrawer() {
const theme = useTheme();
const [open, setOpen] = useState(false);
const handleDrawerOpen = () => setOpen(true);
const handleDrawerClose = () => setOpen(false);
return (
<Box sx={{ display: 'flex' }}>
<AppBar position="fixed" sx={{ zIndex: 1201 }}>
<Toolbar>
<IconButton color="inherit" onClick={handleDrawerOpen} edge="start" sx={{ mr: 2, ...(open && { display: 'none' }) }}>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
可折叠抽屉
</Typography>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
open={open}
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': { width: drawerWidth, boxSizing: 'border-box' },
}}
>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</DrawerHeader>
<Divider />
<List>{/* 菜单项 */}</List>
</Drawer>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Toolbar />
<Typography>主内容</Typography>
</Box>
</Box>
);
}
Tabs 标签页 #
基础用法 #
jsx
import { Tabs, Tab, Box } from '@mui/material';
function BasicTabs() {
const [value, setValue] = useState(0);
const handleChange = (event, newValue) => setValue(newValue);
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange}>
<Tab label="标签一" />
<Tab label="标签二" />
<Tab label="标签三" />
</Tabs>
</Box>
<TabPanel value={value} index={0}>内容一</TabPanel>
<TabPanel value={value} index={1}>内容二</TabPanel>
<TabPanel value={value} index={2}>内容三</TabPanel>
</Box>
);
}
带图标 #
jsx
import { Phone as PhoneIcon, Favorite as FavoriteIcon, PersonPin as PersonPinIcon } from '@mui/icons-material';
<Tabs value={value} onChange={handleChange} aria-label="icon tabs example">
<Tab icon={<PhoneIcon />} aria-label="phone" />
<Tab icon={<FavoriteIcon />} aria-label="favorite" />
<Tab icon={<PersonPinIcon />} aria-label="person" />
</Tabs>
带图标和文字 #
jsx
<Tabs value={value} onChange={handleChange} aria-label="icon label tabs example">
<Tab icon={<PhoneIcon />} label="电话" />
<Tab icon={<FavoriteIcon />} label="收藏" />
<Tab icon={<PersonPinIcon />} label="联系人" />
</Tabs>
居中 #
jsx
<Tabs value={value} onChange={handleChange} centered>
<Tab label="标签一" />
<Tab label="标签二" />
<Tab label="标签三" />
</Tabs>
可滚动 #
jsx
<Tabs value={value} onChange={handleChange} variant="scrollable" scrollButtons="auto">
<Tab label="标签一" />
<Tab label="标签二" />
{/* ... 更多标签 */}
</Tabs>
垂直标签 #
jsx
<Box sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 224 }}>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="标签一" />
<Tab label="标签二" />
<Tab label="标签三" />
</Tabs>
<TabPanel value={value} index={0}>内容一</TabPanel>
<TabPanel value={value} index={1}>内容二</TabPanel>
<TabPanel value={value} index={2}>内容三</TabPanel>
</Box>
Menu 下拉菜单 #
基础用法 #
jsx
import { Menu, MenuItem } from '@mui/material';
function BasicMenu() {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
return (
<>
<Button onClick={handleClick}>打开菜单</Button>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={handleClose}>个人资料</MenuItem>
<MenuItem onClick={handleClose}>我的账户</MenuItem>
<MenuItem onClick={handleClose}>退出登录</MenuItem>
</Menu>
</>
);
}
图标菜单 #
jsx
import { ListItemIcon, ListItemText } from '@mui/material';
import { ContentCopy, ContentCut, ContentPaste } from '@mui/icons-material';
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={handleClose}>
<ListItemIcon><ContentCut fontSize="small" /></ListItemIcon>
<ListItemText>剪切</ListItemText>
</MenuItem>
<MenuItem onClick={handleClose}>
<ListItemIcon><ContentCopy fontSize="small" /></ListItemIcon>
<ListItemText>复制</ListItemText>
</MenuItem>
<MenuItem onClick={handleClose}>
<ListItemIcon><ContentPaste fontSize="small" /></ListItemIcon>
<ListItemText>粘贴</ListItemText>
</MenuItem>
</Menu>
嵌套菜单 #
jsx
import { NestedMenuItem } from 'mui-nested-menu';
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={handleClose}>个人资料</MenuItem>
<NestedMenuItem label="设置" parentMenuOpen={open}>
<MenuItem onClick={handleClose}>账户设置</MenuItem>
<MenuItem onClick={handleClose}>隐私设置</MenuItem>
</NestedMenuItem>
<Divider />
<MenuItem onClick={handleClose}>退出登录</MenuItem>
</Menu>
过渡动画 #
jsx
import { Fade, Grow, Slide } from '@mui/material';
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
{/* 菜单项 */}
</Menu>
Breadcrumbs 面包屑 #
基础用法 #
jsx
import { Breadcrumbs, Link, Typography } from '@mui/material';
import { Home as HomeIcon } from '@mui/icons-material';
<Breadcrumbs aria-label="breadcrumb">
<Link underline="hover" color="inherit" href="/">
首页
</Link>
<Link underline="hover" color="inherit" href="/material-ui/">
Material-UI
</Link>
<Typography color="text.primary">Breadcrumbs</Typography>
</Breadcrumbs>
带图标 #
jsx
<Breadcrumbs aria-label="breadcrumb">
<Link underline="hover" color="inherit" href="/" sx={{ display: 'flex', alignItems: 'center' }}>
<HomeIcon sx={{ mr: 0.5 }} fontSize="inherit" />
首页
</Link>
<Link underline="hover" color="inherit" href="/material-ui/" sx={{ display: 'flex', alignItems: 'center' }}>
<WhatshotIcon sx={{ mr: 0.5 }} fontSize="inherit" />
Material-UI
</Link>
<Typography sx={{ display: 'flex', alignItems: 'center' }} color="text.primary">
<GrainIcon sx={{ mr: 0.5 }} fontSize="inherit" />
Breadcrumbs
</Typography>
</Breadcrumbs>
自定义分隔符 #
jsx
<Breadcrumbs separator="›" aria-label="breadcrumb">
<Link color="inherit" href="/">首页</Link>
<Link color="inherit" href="/material-ui/">Material-UI</Link>
<Typography color="text.primary">Breadcrumbs</Typography>
</Breadcrumbs>
折叠 #
jsx
<Breadcrumbs maxItems={2} aria-label="breadcrumb">
<Link color="inherit" href="/">首页</Link>
<Link color="inherit" href="/material-ui/">Material-UI</Link>
<Link color="inherit" href="/material-ui/getting-started/">开始使用</Link>
<Typography color="text.primary">Breadcrumbs</Typography>
</Breadcrumbs>
BottomNavigation 底部导航 #
基础用法 #
jsx
import { BottomNavigation, BottomNavigationAction } from '@mui/material';
import { Restore as RestoreIcon, Favorite as FavoriteIcon, LocationOn as LocationOnIcon } from '@mui/icons-material';
function SimpleBottomNavigation() {
const [value, setValue] = useState(0);
return (
<BottomNavigation
showLabels
value={value}
onChange={(event, newValue) => setValue(newValue)}
>
<BottomNavigationAction label="恢复" icon={<RestoreIcon />} />
<BottomNavigationAction label="收藏" icon={<FavoriteIcon />} />
<BottomNavigationAction label="附近" icon={<LocationOnIcon />} />
</BottomNavigation>
);
}
无标签 #
jsx
<BottomNavigation value={value} onChange={(event, newValue) => setValue(newValue)}>
<BottomNavigationAction icon={<RestoreIcon />} />
<BottomNavigationAction icon={<FavoriteIcon />} />
<BottomNavigationAction icon={<LocationOnIcon />} />
<BottomNavigationAction icon={<FolderIcon />} />
</BottomNavigation>
Stepper 步骤器 #
水平步骤器 #
jsx
import { Stepper, Step, StepLabel, Button, Box } from '@mui/material';
const steps = ['选择活动', '创建广告组', '创建广告'];
function HorizontalLinearStepper() {
const [activeStep, setActiveStep] = useState(0);
const handleNext = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const handleBack = () => setActiveStep((prevActiveStep) => prevActiveStep - 1);
return (
<Box sx={{ width: '100%' }}>
<Stepper activeStep={activeStep}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
<Button disabled={activeStep === 0} onClick={handleBack} sx={{ mr: 1 }}>
上一步
</Button>
<Box sx={{ flex: '1 1 auto' }} />
<Button onClick={handleNext}>
{activeStep === steps.length - 1 ? '完成' : '下一步'}
</Button>
</Box>
</Box>
);
}
垂直步骤器 #
jsx
import { StepContent } from '@mui/material';
function VerticalLinearStepper() {
const [activeStep, setActiveStep] = useState(0);
return (
<Stepper activeStep={activeStep} orientation="vertical">
{steps.map((step, index) => (
<Step key={step.label}>
<StepLabel>{step.label}</StepLabel>
<StepContent>
<Typography>{step.description}</Typography>
<Box sx={{ mb: 2 }}>
<Button variant="contained" onClick={() => setActiveStep((prev) => prev + 1)} sx={{ mt: 1, mr: 1 }}>
{index === steps.length - 1 ? '完成' : '继续'}
</Button>
<Button disabled={index === 0} onClick={() => setActiveStep((prev) => prev - 1)} sx={{ mt: 1, mr: 1 }}>
返回
</Button>
</Box>
</StepContent>
</Step>
))}
</Stepper>
);
}
带图标 #
jsx
import { StepIcon } from '@mui/material';
<Stepper alternativeLabel activeStep={activeStep}>
{steps.map((label) => (
<Step key={label}>
<StepLabel StepIconComponent={CustomStepIcon}>{label}</StepLabel>
</Step>
))}
</Stepper>
实战示例:完整布局 #
jsx
function DashboardLayout() {
const [mobileOpen, setMobileOpen] = useState(false);
const [selectedTab, setSelectedTab] = useState(0);
const drawerWidth = 240;
const handleDrawerToggle = () => setMobileOpen(!mobileOpen);
const drawer = (
<div>
<Toolbar />
<Divider />
<List>
{['首页', '用户管理', '订单管理', '统计分析'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</div>
);
return (
<Box sx={{ display: 'flex' }}>
<AppBar position="fixed" sx={{ width: { sm: `calc(100% - ${drawerWidth}px)` }, ml: { sm: `${drawerWidth}px` } }}>
<Toolbar>
<IconButton color="inherit" edge="start" onClick={handleDrawerToggle} sx={{ mr: 2, display: { sm: 'none' } }}>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
管理后台
</Typography>
</Toolbar>
</AppBar>
<Box component="nav" sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}>
<Drawer variant="temporary" open={mobileOpen} onClose={handleDrawerToggle} ModalProps={{ keepMounted: true }} sx={{ display: { xs: 'block', sm: 'none' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth } }}>
{drawer}
</Drawer>
<Drawer variant="permanent" sx={{ display: { xs: 'none', sm: 'block' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth } }} open>
{drawer}
</Drawer>
</Box>
<Box component="main" sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` } }}>
<Toolbar />
<Typography>主内容区域</Typography>
</Box>
</Box>
);
}
下一步 #
继续学习 表面组件,了解 Paper、Card、Accordion 等表面组件!
最后更新:2026-03-28