Tabs

Tabs

Tabs는 서로 다른 뷰 사이를 쉽게 탐색하고 전환할 수 있게 해주는 컴포넌트예요. 같은 계층에 있는 관련 콘텐츠 그룹을 정리하고, 그 사이를 이동할 수 있게 도와줍니다. 같은 레벨의 콘텐츠가 여러 개 있을 때 공간을 효율적이고 깔끔하게 만들고 싶다면 아주 유용하답니다.

출처: 문서

본문

Tabs는 서로 다른 뷰를 쉽게 탐색하고 전환할 수 있게 해줘요.

Tabs는 같은 계층에 있고 서로 관련된 콘텐츠 그룹을 정리하고, 그 사이를 이동할 수 있게 해줍니다.

소개 (Introduction)

Tabs는 서로 관련된 컴포넌트 모음으로 구현돼요:

  • <Tab /> - 탭 요소 자체. 탭을 클릭하면 해당하는 패널이 표시돼요.
  • <Tabs /> - 탭들을 담는 컨테이너. 탭 사이의 포커스와 키보드 내비게이션 처리를 담당해요.
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

interface TabPanelProps {
  children?: React.ReactNode;
  index: number;
  value: number;
}

function CustomTabPanel(props: TabPanelProps) {
  const { children, value, index, ...other } = props;

  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      tabIndex={0}
      id={`simple-tabpanel-${index}`}
      aria-labelledby={`simple-tab-${index}`}
      {...other}
    >
      {value === index && <Box sx={{ p: 3 }}>{children}</Box>}
    </div>
  );
}

function a11yProps(index: number) {
  return {
    id: `simple-tab-${index}`,
    'aria-controls': `simple-tabpanel-${index}`,
  };
}

export default function BasicTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
        <Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
          <Tab label="Item One" {...a11yProps(0)} />
          <Tab label="Item Two" {...a11yProps(1)} />
          <Tab label="Item Three" {...a11yProps(2)} />
        </Tabs>
      </Box>
      <CustomTabPanel value={value} index={0}>
        Item One
      </CustomTabPanel>
      <CustomTabPanel value={value} index={1}>
        Item Two
      </CustomTabPanel>
      <CustomTabPanel value={value} index={2}>
        Item Three
      </CustomTabPanel>
    </Box>
  );
}

기본 사용법 (Basics)

import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';

실험적 API (Experimental API)

@mui/lab은 접근성 있는 탭을 구현하기 위해 props를 주입하는 유틸리티 컴포넌트를 제공하는데, WAI-ARIA Authoring Practices를 따릅니다:

  • <TabList /> - 탭들을 담는 컨테이너. 탭 사이의 포커스와 키보드 내비게이션 처리를 담당해요.
  • <TabPanel /> - 탭과 연관된 콘텐츠를 담는 카드.
  • <TabContext /> - Tab List와 Tab Panel 컴포넌트를 감싸는 최상위 컴포넌트.
import * as React from 'react';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import TabContext from '@mui/lab/TabContext';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';

export default function LabTabs() {
  const [value, setValue] = React.useState('1');

  const handleChange = (event: React.SyntheticEvent, newValue: string) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%', typography: 'body1' }}>
      <TabContext value={value}>
        <TabList
          onChange={handleChange}
          aria-label="lab tabs"
          sx={{ borderBottom: 1, borderColor: 'divider' }}
        >
          <Tab label="Item One" value="1" />
          <Tab label="Item Two" value="2" />
        </TabList>
        <TabPanel value="1" tabIndex={0}>
          Item One
        </TabPanel>
        <TabPanel value="2" tabIndex={0}>
          Item Two
        </TabPanel>
      </TabContext>
    </Box>
  );
}

패널을 항상 마운트해 두기 (Keep panels always mounted)

패널을 마운트해 두면 상태가 보존되고 이후 탭 변경이 더 빨라져요. 다만 모든 패널이 초기 로드에 렌더링되고, 메모리를 소비하며, 숨겨져 있는 동안에도 effect가 계속 실행될 수 있어요. 상태가 있는(stateful) 패널이나 자주 접근하는 패널에 이 방식을 선택적으로 사용하세요.

lab API로 (With lab API)

@mui/lab API를 사용할 때는 각 TabPanel에 keepMounted를 전달하세요.

import * as React from 'react';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import TextField from '@mui/material/TextField';
import TabContext from '@mui/lab/TabContext';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';

export default function KeepMountedLabTabs() {
  const [value, setValue] = React.useState('1');

  const handleChange = (event: React.SyntheticEvent, newValue: string) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <TabContext value={value}>
        <TabList
          onChange={handleChange}
          aria-label="keep mounted tabs example"
          sx={{ borderBottom: 1, borderColor: 'divider' }}
        >
          <Tab label="Item One" value="1" />
          <Tab label="Item Two" value="2" />
        </TabList>
        <TabPanel value="1" keepMounted>
          <TextField label="Item One input" />
        </TabPanel>
        <TabPanel value="2" keepMounted>
          <TextField label="Item Two input" />
        </TabPanel>
      </TabContext>
    </Box>
  );
}

표준 API로 (With standard API)

표준 API를 사용할 때는 패널 자식을 조건 없이(unconditionally) 렌더링하고, hidden 속성으로 가시성을 제어하세요.

import * as React from 'react';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import TextField from '@mui/material/TextField';

interface TabPanelProps {
  children?: React.ReactNode;
  index: number;
  value: number;
}

function CustomTabPanel(props: TabPanelProps) {
  const { children, value, index, ...other } = props;

  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      id={`keep-mounted-tabpanel-${index}`}
      aria-labelledby={`keep-mounted-tab-${index}`}
      {...other}
    >
      <Box sx={{ p: 3 }}>{children}</Box>
    </div>
  );
}

function a11yProps(index: number) {
  return {
    id: `keep-mounted-tab-${index}`,
    'aria-controls': `keep-mounted-tabpanel-${index}`,
  };
}

export default function KeepMountedStandardTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        aria-label="keep mounted tabs example"
        sx={{ borderBottom: 1, borderColor: 'divider' }}
      >
        <Tab label="Item One" {...a11yProps(0)} />
        <Tab label="Item Two" {...a11yProps(1)} />
      </Tabs>
      <CustomTabPanel value={value} index={0}>
        <TextField label="Item One input" />
      </CustomTabPanel>
      <CustomTabPanel value={value} index={1}>
        <TextField label="Item Two input" />
      </CustomTabPanel>
    </Box>
  );
}

줄 바꿈된 라벨 (Wrapped labels)

긴 라벨은 탭에서 자동으로 줄 바꿈(wrap)돼요. 라벨이 탭에 비해 너무 길면 넘칠(overflow) 테고, 텍스트가 보이지 않게 될 거예요.

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function TabsWrappedLabel() {
  const [value, setValue] = React.useState('one');

  const handleChange = (event: React.SyntheticEvent, newValue: string) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        aria-label="wrapped label tabs example"
      >
        <Tab
          value="one"
          label="New Arrivals in the Longest Text of Nonfiction that should appear in the next line"
          wrapped
        />
        <Tab value="two" label="Item Two" />
        <Tab value="three" label="Item Three" />
      </Tabs>
    </Box>
  );
}

색상이 있는 탭 (Colored tab)

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function ColorTabs() {
  const [value, setValue] = React.useState('one');

  const handleChange = (event: React.SyntheticEvent, newValue: string) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        textColor="secondary"
        indicatorColor="secondary"
        aria-label="secondary tabs example"
      >
        <Tab value="one" label="Item One" />
        <Tab value="two" label="Item Two" />
        <Tab value="three" label="Item Three" />
      </Tabs>
    </Box>
  );
}

비활성화된 탭 (Disabled tab)

disabled prop을 설정하면 탭을 비활성화할 수 있어요.

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';

export default function DisabledTabs() {
  const [value, setValue] = React.useState(2);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Tabs value={value} onChange={handleChange} aria-label="disabled tabs example">
      <Tab label="Active" />
      <Tab label="Disabled" disabled />
      <Tab label="Active" />
    </Tabs>
  );
}

고정 탭 (Fixed tabs)

고정(fixed) 탭은 탭 수가 제한되어 있을 때, 그리고 일관된 배치가 근육 기억(muscle memory)에 도움이 될 때 사용해야 해요.

전체 너비 (Full width)

variant="fullWidth" prop은 더 작은 뷰에 사용해야 해요.

import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';

interface TabPanelProps {
  children?: React.ReactNode;
  dir?: string;
  index: number;
  value: number;
}

function TabPanel(props: TabPanelProps) {
  const { children, value, index, ...other } = props;

  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      tabIndex={0}
      id={`full-width-tabpanel-${index}`}
      aria-labelledby={`full-width-tab-${index}`}
      {...other}
    >
      {value === index && (
        <Box sx={{ p: 3 }}>
          <Typography>{children}</Typography>
        </Box>
      )}
    </div>
  );
}

function a11yProps(index: number) {
  return {
    id: `full-width-tab-${index}`,
    'aria-controls': `full-width-tabpanel-${index}`,
  };
}

export default function FullWidthTabs() {
  const theme = useTheme();
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ bgcolor: 'background.paper', width: 500 }}>
      <AppBar position="static">
        <Tabs
          value={value}
          onChange={handleChange}
          indicatorColor="secondary"
          textColor="inherit"
          variant="fullWidth"
          aria-label="full width tabs example"
        >
          <Tab label="Item One" {...a11yProps(0)} />
          <Tab label="Item Two" {...a11yProps(1)} />
          <Tab label="Item Three" {...a11yProps(2)} />
        </Tabs>
      </AppBar>
      <TabPanel value={value} index={0} dir={theme.direction}>
        Item One
      </TabPanel>
      <TabPanel value={value} index={1} dir={theme.direction}>
        Item Two
      </TabPanel>
      <TabPanel value={value} index={2} dir={theme.direction}>
        Item Three
      </TabPanel>
    </Box>
  );
}

가운데 정렬 (Centered)

centered prop은 더 큰 뷰에 사용해야 해요.

import * as React from 'react';
import Box from '@mui/material/Box';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';

export default function CenteredTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
      <Tabs value={value} onChange={handleChange} centered>
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
      </Tabs>
    </Box>
  );
}

스크롤 가능한 탭 (Scrollable tabs)

자동 스크롤 버튼 (Automatic scroll buttons)

variant="scrollable"과 scrollButtons="auto" props를 사용하면 데스크톱에서는 왼쪽·오른쪽 스크롤 버튼을 표시하고 모바일에서는 숨길 수 있어요:

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function ScrollableTabsButtonAuto() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ maxWidth: { xs: 320, sm: 480 }, bgcolor: 'background.paper' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        variant="scrollable"
        scrollButtons="auto"
        aria-label="scrollable auto tabs example"
      >
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
        <Tab label="Item Four" />
        <Tab label="Item Five" />
        <Tab label="Item Six" />
        <Tab label="Item Seven" />
      </Tabs>
    </Box>
  );
}

강제 스크롤 버튼 (Forced scroll buttons)

scrollButtons={true}과 allowScrollButtonsMobile prop을 적용하면 모든 뷰포트에서 왼쪽·오른쪽 스크롤 버튼을 표시할 수 있어요:

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function ScrollableTabsButtonForce() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ maxWidth: { xs: 320, sm: 480 }, bgcolor: 'background.paper' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        variant="scrollable"
        scrollButtons
        allowScrollButtonsMobile
        aria-label="scrollable force tabs example"
      >
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
        <Tab label="Item Four" />
        <Tab label="Item Five" />
        <Tab label="Item Six" />
        <Tab label="Item Seven" />
      </Tabs>
    </Box>
  );
}

버튼이 항상 보이도록 하고 싶다면, 불투명도(opacity)를 커스터마이즈해야 해요.

.MuiTabs-scrollButtons.Mui-disabled {
  opacity: 0.3;
}
import * as React from 'react';
import Box from '@mui/material/Box';
import Tabs, { tabsClasses } from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';

export default function ScrollableTabsButtonVisible() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box
      sx={{
        flexGrow: 1,
        maxWidth: { xs: 320, sm: 480 },
        bgcolor: 'background.paper',
      }}
    >
      <Tabs
        value={value}
        onChange={handleChange}
        variant="scrollable"
        scrollButtons
        aria-label="visible arrows tabs example"
        sx={{
          [`& .${tabsClasses.scrollButtons}`]: {
            '&.Mui-disabled': { opacity: 0.3 },
          },
        }}
      >
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
        <Tab label="Item Four" />
        <Tab label="Item Five" />
        <Tab label="Item Six" />
        <Tab label="Item Seven" />
      </Tabs>
    </Box>
  );
}

스크롤 버튼 비활성화 (Prevent scroll buttons)

왼쪽·오른쪽 스크롤 버튼은 scrollButtons={false}로 절대 표시되지 않아요. 모든 스크롤은 사용자 에이전트의 스크롤 메커니즘(예: 왼쪽/오른쪽 스와이프, Shift+마우스 휠 등)으로 시작되어야 해요.

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function ScrollableTabsButtonPrevent() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ maxWidth: { xs: 320, sm: 480 }, bgcolor: 'background.paper' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        variant="scrollable"
        scrollButtons={false}
        aria-label="scrollable prevent tabs example"
      >
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
        <Tab label="Item Four" />
        <Tab label="Item Five" />
        <Tab label="Item Six" />
        <Tab label="Item Seven" />
      </Tabs>
    </Box>
  );
}

커스터마이즈 (Customization)

여기 컴포넌트를 커스터마이즈하는 예시가 있어요. 이에 대해 더 자세히 알아보려면 오버라이드 문서 페이지를 참고하세요.

import * as React from 'react';
import { styled } from '@mui/material/styles';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

const AntTabs = styled(Tabs)({
  borderBottom: '1px solid #e8e8e8',
  '& .MuiTabs-indicator': {
    backgroundColor: '#1890ff',
  },
});

const AntTab = styled((props: StyledTabProps) => <Tab disableRipple {...props} />)(
  ({ theme }) => ({
    textTransform: 'none',
    minWidth: 0,
    [theme.breakpoints.up('sm')]: {
      minWidth: 0,
    },
    fontWeight: theme.typography.fontWeightRegular,
    marginRight: theme.spacing(1),
    color: 'rgba(0, 0, 0, 0.85)',
    fontFamily: [
      '-apple-system',
      'BlinkMacSystemFont',
      '"Segoe UI"',
      'Roboto',
      '"Helvetica Neue"',
      'Arial',
      'sans-serif',
      '"Apple Color Emoji"',
      '"Segoe UI Emoji"',
      '"Segoe UI Symbol"',
    ].join(','),
    '&:hover': {
      color: '#40a9ff',
      opacity: 1,
    },
    '&.Mui-selected': {
      color: '#1890ff',
      fontWeight: theme.typography.fontWeightMedium,
    },
    '&.Mui-focusVisible': {
      backgroundColor: '#d1eaff',
    },
  }),
);

interface StyledTabsProps {
  children?: React.ReactNode;
  value: number;
  onChange: (event: React.SyntheticEvent, newValue: number) => void;
}

const StyledTabs = styled((props: StyledTabsProps) => (
  <Tabs
    {...props}
    slotProps={{
      indicator: { children: <span className="MuiTabs-indicatorSpan" /> },
    }}
  />
))({
  '& .MuiTabs-indicator': {
    display: 'flex',
    justifyContent: 'center',
    backgroundColor: 'transparent',
  },
  '& .MuiTabs-indicatorSpan': {
    maxWidth: 40,
    width: '100%',
    backgroundColor: '#635ee7',
  },
});

interface StyledTabProps {
  label: string;
}

const StyledTab = styled((props: StyledTabProps) => (
  <Tab disableRipple {...props} />
))(({ theme }) => ({
  textTransform: 'none',
  fontWeight: theme.typography.fontWeightRegular,
  fontSize: theme.typography.pxToRem(15),
  marginRight: theme.spacing(1),
  color: 'rgba(255, 255, 255, 0.7)',
  '&.Mui-selected': {
    color: '#fff',
  },
  '&.Mui-focusVisible': {
    backgroundColor: 'rgba(100, 95, 228, 0.32)',
  },
}));

export default function CustomizedTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Box sx={{ bgcolor: '#fff' }}>
        <AntTabs value={value} onChange={handleChange} aria-label="ant example">
          <AntTab label="Tab 1" />
          <AntTab label="Tab 2" />
          <AntTab label="Tab 3" />
        </AntTabs>
        <Box sx={{ p: 3 }} />
      </Box>
      <Box sx={{ bgcolor: '#2e1534' }}>
        <StyledTabs
          value={value}
          onChange={handleChange}
          aria-label="styled tabs example"
        >
          <StyledTab label="Workflows" />
          <StyledTab label="Datasets" />
          <StyledTab label="Connections" />
        </StyledTabs>
        <Box sx={{ p: 3 }} />
      </Box>
    </Box>
  );
}

🎨 영감이 필요하다면 MUI Treasury의 커스터마이즈 예제를 확인해 보세요.

세로 탭 (Vertical tabs)

기본 가로 탭 대신 세로 탭을 만들려면 orientation="vertical"이 있어요:

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';

interface TabPanelProps {
  children?: React.ReactNode;
  index: number;
  value: number;
}

function TabPanel(props: TabPanelProps) {
  const { children, value, index, ...other } = props;

  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      tabIndex={0}
      id={`vertical-tabpanel-${index}`}
      aria-labelledby={`vertical-tab-${index}`}
      {...other}
    >
      {value === index && (
        <Box sx={{ p: 3 }}>
          <Typography>{children}</Typography>
        </Box>
      )}
    </div>
  );
}

function a11yProps(index: number) {
  return {
    id: `vertical-tab-${index}`,
    'aria-controls': `vertical-tabpanel-${index}`,
  };
}

export default function VerticalTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box
      sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 224 }}
    >
      <Tabs
        orientation="vertical"
        variant="scrollable"
        value={value}
        onChange={handleChange}
        aria-label="Vertical tabs example"
        sx={{ borderRight: 1, borderColor: 'divider' }}
      >
        <Tab label="Item One" {...a11yProps(0)} />
        <Tab label="Item Two" {...a11yProps(1)} />
        <Tab label="Item Three" {...a11yProps(2)} />
        <Tab label="Item Four" {...a11yProps(3)} />
        <Tab label="Item Five" {...a11yProps(4)} />
        <Tab label="Item Six" {...a11yProps(5)} />
        <Tab label="Item Seven" {...a11yProps(6)} />
      </Tabs>
      <TabPanel value={value} index={0}>
        Item One
      </TabPanel>
      <TabPanel value={value} index={1}>
        Item Two
      </TabPanel>
      <TabPanel value={value} index={2}>
        Item Three
      </TabPanel>
      <TabPanel value={value} index={3}>
        Item Four
      </TabPanel>
      <TabPanel value={value} index={4}>
        Item Five
      </TabPanel>
      <TabPanel value={value} index={5}>
        Item Six
      </TabPanel>
      <TabPanel value={value} index={6}>
        Item Seven
      </TabPanel>
    </Box>
  );
}

visibleScrollbar로 스크롤바를 복원할 수 있다는 점도 알아두세요.

기본적으로 탭은 button 요소를 사용하지만, 커스텀 태그나 컴포넌트를 제공할 수도 있어요. 여기 탭 기반 내비게이션을 구현하는 예시가 있어요:

import * as React from 'react';
import Box from '@mui/material/Box';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';

function samePageLinkNavigation(
  event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
) {
  if (
    event.defaultPrevented ||
    event.button !== 0 || // ignore everything but left-click
    event.metaKey ||
    event.ctrlKey ||
    event.altKey ||
    event.shiftKey
  ) {
    return false;
  }
  return true;
}

interface LinkTabProps {
  label?: string;
  href?: string;
  selected?: boolean;
}

function LinkTab(props: LinkTabProps) {
  return (
    <Tab
      component="a"
      onClick={(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
        // Routing libraries handle this, you can remove the onClick handle when using them.
        if (samePageLinkNavigation(event)) {
          event.preventDefault();
        }
      }}
      aria-current={props.selected && 'page'}
      {...props}
    />
  );
}

export default function NavTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    // event.type can be equal to focus with selectionFollowsFocus.
    if (
      event.type !== 'click' ||
      (event.type === 'click' &&
        samePageLinkNavigation(
          event as React.MouseEvent<HTMLAnchorElement, MouseEvent>,
        ))
    ) {
      setValue(newValue);
    }
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Tabs
        value={value}
        onChange={handleChange}
        aria-label="nav tabs example"
        role="navigation"
      >
        <LinkTab label="Page One" href="/drafts" />
        <LinkTab label="Page Two" href="/trash" />
        <LinkTab label="Page Three" href="/spam" />
      </Tabs>
    </Box>
  );
}

서드파티 라우팅 라이브러리 (Third-party routing library)

자주 쓰이는 사용 사례 중 하나는 서버로의 HTTP 왕복(round-trip) 없이 클라이언트에서만 내비게이션을 수행하는 것이에요. Tab 컴포넌트는 이 사용 사례를 처리하기 위한 component prop을 제공해요. 여기 더 자세한 가이드가 있어요.

아이콘 탭 (Icon tabs)

탭 라벨은 모두 아이콘이거나 모두 텍스트일 수 있어요.

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import PhoneIcon from '@mui/icons-material/Phone';
import FavoriteIcon from '@mui/icons-material/Favorite';
import PersonPinIcon from '@mui/icons-material/PersonPin';

export default function IconTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <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>
  );
}

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import PhoneIcon from '@mui/icons-material/Phone';
import FavoriteIcon from '@mui/icons-material/Favorite';
import PersonPinIcon from '@mui/icons-material/PersonPin';

export default function IconLabelTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Tabs value={value} onChange={handleChange} aria-label="icon label tabs example">
      <Tab icon={<PhoneIcon />} label="RECENTS" />
      <Tab icon={<FavoriteIcon />} label="FAVORITES" />
      <Tab icon={<PersonPinIcon />} label="NEARBY" />
    </Tabs>
  );
}

아이콘 위치 (Icon position)

기본적으로 아이콘은 탭의 top에 위치해요. 지원되는 다른 위치는 start, end, bottom이에요.

import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import PhoneIcon from '@mui/icons-material/Phone';
import FavoriteIcon from '@mui/icons-material/Favorite';
import PersonPinIcon from '@mui/icons-material/PersonPin';
import PhoneMissedIcon from '@mui/icons-material/PhoneMissed';

export default function IconPositionTabs() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Tabs
      value={value}
      onChange={handleChange}
      aria-label="icon position tabs example"
    >
      <Tab icon={<PhoneIcon />} label="top" />
      <Tab icon={<PhoneMissedIcon />} iconPosition="start" label="start" />
      <Tab icon={<FavoriteIcon />} iconPosition="end" label="end" />
      <Tab icon={<PersonPinIcon />} iconPosition="bottom" label="bottom" />
    </Tabs>
  );
}

접근성 (Accessibility)

(WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)

보조 기술(assistive technologies)에 필요한 정보를 제공하려면 다음 단계가 필요해요:

  1. aria-label이나 aria-labelledby로 Tabs에 라벨을 붙이세요.
  2. Tab들은 올바른 id, aria-controls, aria-labelledby를 설정해서 상응하는 [role="tabpanel"]에 연결해야 해요.
  3. 탭 패널에 포커스 가능한 콘텐츠가 없거나, 의미 있는 콘텐츠를 담은 첫 번째 요소가 포커스 가능하지 않다면 패널에 tabIndex={0}을 설정하세요.

현재 구현에 대한 예시는 이 페이지의 데모에서 찾을 수 있어요. 우리는 별도의 작업이 필요 없는 실험적 API를 @mui/lab에 공개하기도 했어요.

키보드 내비게이션 (Keyboard navigation)

컴포넌트들은 "수동 활성화(manual activation)" 동작으로 키보드 내비게이션을 구현해요. "포커스를 선택이 자동으로 따르는(selection automatically follows focus)" 동작으로 전환하려면 Tabs 컴포넌트에 selectionFollowsFocus를 전달해야 해요. WAI-ARIA authoring practices에는 언제 선택이 자동으로 포커스를 따르게 할지 결정하는 방법에 대한 자세한 가이드가 있어요.

데모 (Demo)

다음 두 데모는 키보드 내비게이션 동작에서만 달라요. 탭에 포커스를 주고 화살표 키로 이동해 보면 차이를 느낄 수 있어요. 예를 들어 Arrow Left를 눌러보세요.

/* Tabs where selection follows focus */
<Tabs selectionFollowsFocus />
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function AccessibleTabs1() {
  const [value, setValue] = React.useState(0);
  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Tabs
        onChange={handleChange}
        value={value}
        aria-label="Tabs where selection follows focus"
        selectionFollowsFocus
      >
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
      </Tabs>
    </Box>
  );
}

/* Tabs where each tab needs to be selected manually */
<Tabs />
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';

export default function AccessibleTabs2() {
  const [value, setValue] = React.useState(0);
  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
    setValue(newValue);
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Tabs
        onChange={handleChange}
        value={value}
        aria-label="Tabs where each tab needs to be selected manually"
      >
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
      </Tabs>
    </Box>
  );
}

Tab API

데모 (Demos)

이 React 컴포넌트의 사용 예시와 세부 내용은 컴포넌트 데모 페이지에서 확인하세요:

Import

import Tab from '@mui/material/Tab';
// or
import { Tab } from '@mui/material';

Props

Name Type Default Required Description
children unsupportedProp - No
classes object - No Override or extend the styles applied to the component.
disabled bool false No
disableFocusRipple bool false No
disableRipple bool false No
icon element | string - No
iconPosition 'bottom' | 'end' | 'start' | 'top' 'top' No
label node - No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
value any - No
wrapped bool false No

Note: The ref is forwarded to the root element (HTMLButtonElement).

Any other props supplied will be provided to the root element (ButtonBase).

상속 (Inheritance)

위에서 명시적으로 문서화되지는 않았지만, ButtonBase 컴포넌트의 props도 Tab에서 사용할 수 있어요.

테마 기본 props (Theme default props)

테마와 함께 MuiTab을 사용하면 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

규칙 이름 (Rule name)

Global class Rule name Description
.Mui-disabled - State class applied to the root element if disabled={true} (controlled by the Tabs component).
- fullWidth Styles applied to the root element if fullWidth={true} (controlled by the Tabs component).
- icon Styles applied to the icon HTML element if both icon and label are provided.
- labelIcon Styles applied to the root element if both icon and label are provided.
- root Styles applied to the root element.
.Mui-selected - State class applied to the root element if selected={true} (controlled by the Tabs component).
- textColorInherit Styles applied to the root element if the parent Tabs has textColor="inherit".
- textColorPrimary Styles applied to the root element if the parent Tabs has textColor="primary".
- textColorSecondary Styles applied to the root element if the parent Tabs has textColor="secondary".
- wrapped Styles applied to the root element if wrapped={true}.

소스 코드 (Source code)

이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보는 것을 고려해보세요.

TabContext API

데모 (Demos)

이 React 컴포넌트의 사용 예시와 세부 내용은 컴포넌트 데모 페이지에서 확인하세요:

Import

import TabContext from '@mui/lab/TabContext';
// or
import { TabContext } from '@mui/lab';

Props

Name Type Default Required Description
value number | string - Yes
children node - No

Note: The ref is forwarded to the root element.

Any other props supplied will be provided to the root element (native element).

소스 코드 (Source code)

이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보는 것을 고려해보세요.

TabList API

데모 (Demos)

이 React 컴포넌트의 사용 예시와 세부 내용은 컴포넌트 데모 페이지에서 확인하세요:

Import

import TabList from '@mui/lab/TabList';
// or
import { TabList } from '@mui/lab';

Props

Name Type Default Required Description
children node - No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (Tabs).

상속 (Inheritance)

위에서 명시적으로 문서화되지는 않았지만, Tabs 컴포넌트의 props도 TabList에서 사용할 수 있어요.

CSS

규칙 이름 (Rule name)

Global class Rule name Description
- centered Styles applied to the flex container element if centered={true} & !variant="scrollable".
- fixed Styles applied to the tablist element if !variant="scrollable".
- hideScrollbar Styles applied to the tablist element if variant="scrollable" and visibleScrollbar={false}.
- indicator Styles applied to the TabIndicator component.
- list Styles applied to the list element.
- root Styles applied to the root element.
- scrollableX Styles applied to the tablist element if variant="scrollable" and orientation="horizontal".
- scrollableY Styles applied to the tablist element if variant="scrollable" and orientation="vertical".
- scrollButtons Styles applied to the ScrollButtonComponent component.
- scrollButtonsHideMobile Styles applied to the ScrollButtonComponent component if allowScrollButtonsMobile={true}.
- scroller Styles applied to the tablist element.
- vertical Styles applied to the root element if orientation="vertical".

소스 코드 (Source code)

이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보는 것을 고려해보세요.

TabPanel API

데모 (Demos)

이 React 컴포넌트의 사용 예시와 세부 내용은 컴포넌트 데모 페이지에서 확인하세요:

Import

import TabPanel from '@mui/lab/TabPanel';
// or
import { TabPanel } from '@mui/lab';

Props

Name Type Default Required Description
value number | string - Yes
children node - No
classes object - No Override or extend the styles applied to the component.
keepMounted bool false No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (native element).

CSS

규칙 이름 (Rule name)

Global class Rule name Description
- hidden State class applied to the root div element if hidden={true}.
- root Styles applied to the root element.

소스 코드 (Source code)

이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보는 것을 고려해보세요.

TabScrollButton API

데모 (Demos)

이 React 컴포넌트의 사용 예시와 세부 내용은 컴포넌트 데모 페이지에서 확인하세요:

Import

import TabScrollButton from '@mui/material/TabScrollButton';
// or
import { TabScrollButton } from '@mui/material';

Props

Name Type Default Required Description
direction 'left' | 'right' - Yes
orientation 'horizontal' | 'vertical' - Yes
children node - No
classes object - No Override or extend the styles applied to the component.
disabled bool false No
slotProps { endScrollButtonIcon?: func | object, startScrollButtonIcon?: func | object } {} No
slots { EndScrollButtonIcon?: elementType, StartScrollButtonIcon?: elementType } {} No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (native element).

테마 기본 props (Theme default props)

테마와 함께 MuiTabScrollButton을 사용하면 이 컴포넌트의 기본 props를 변경할 수 있어요.

CSS

규칙 이름 (Rule name)

Global class Rule name Description
.Mui-disabled - State class applied to the root element if disabled={true}.
- root Styles applied to the root element.
- vertical Styles applied to the root element if orientation="vertical".

소스 코드 (Source code)

이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보는 것을 고려해보세요.

Tabs API

데모 (Demos)

이 React 컴포넌트의 사용 예시와 세부 내용은 컴포넌트 데모 페이지에서 확인하세요:

Import

import Tabs from '@mui/material/Tabs';
// or
import { Tabs } from '@mui/material';

Props

Name Type Default Required Description
action ref - No
allowScrollButtonsMobile bool false No
aria-label string - No
aria-labelledby string - No
centered bool false No
children node - No
classes object - No Override or extend the styles applied to the component.
component elementType - No
indicatorColor 'primary' | 'secondary' | string 'primary' No
onChange function(event: React.SyntheticEvent, value: any) => void - No
orientation 'horizontal' | 'vertical' 'horizontal' No
scrollButtons 'auto' | false | true 'auto' No
selectionFollowsFocus bool - No
slotProps { endScrollButtonIcon?: func | object, indicator?: func | object, list?: func | object, root?: func | object, scrollbar?: func | object, scrollButtons?: func | object, scroller?: func | object, startScrollButtonIcon?: func | object } {} No
slots { endScrollButtonIcon?: elementType, indicator?: elementType, list?: elementType, root?: elementType, scrollbar?: elementType, scrollButtons?: elementType, scroller?: elementType, startScrollButtonIcon?: elementType } {} No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
textColor 'inherit' | 'primary' | 'secondary' 'primary' No
value any - No
variant 'fullWidth' | 'scrollable' | 'standard' 'standard' No
visibleScrollbar bool false No

Note: The ref is forwarded to the root element (HTMLDivElement).

Any other props supplied will be provided to the root element (native element).

테마 기본 props (Theme default props)

테마와 함께 MuiTabs를 사용하면 이 컴포넌트의 기본 props를 변경할 수 있어요.

슬롯 (Slots)

Name Default Class Description
root div .MuiTabs-root The component used for the popper.
scroller div .MuiTabs-scroller The component used for the scroller.
list div .MuiTabs-list The component used for the flex container.
scrollbar ScrollbarSize - The component used for the scroller.
indicator span .MuiTabs-indicator The component used for the tab indicator.
scrollButtons TabScrollButton .MuiTabs-scrollButtons The component used for the scroll button.
startScrollButtonIcon KeyboardArrowLeft - The component used for the start scroll button icon.
endScrollButtonIcon KeyboardArrowRight - The component used for the end scroll button icon.

CSS

규칙 이름 (Rule name)

Global class Rule name Description
- centered Styles applied to the flex container element if centered={true} & !variant="scrollable".
- fixed Styles applied to the tablist element if !variant="scrollable".
- hideScrollbar Styles applied to the tablist element if variant="scrollable" and visibleScrollbar={false}.
- scrollableX Styles applied to the tablist element if variant="scrollable" and orientation="horizontal".
- scrollableY Styles applied to the tablist element if variant="scrollable" and orientation="vertical".
- scrollButtonsHideMobile Styles applied to the ScrollButtonComponent component if allowScrollButtonsMobile={true}.
- vertical Styles applied to the root element if orientation="vertical".

소스 코드 (Source code)

이 페이지에서 원하는 정보를 찾지 못했다면, 더 자세한 내용을 위해 컴포넌트 구현을 살펴보는 것을 고려해보세요.

더 알아보기 (Learn more)