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>

基础用法 #

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>

基础用法 #

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