Badge

Badge (배지)

Badge는 자식의 오른쪽 상단에 작은 배지를 생성해요. 알림 개수나 상태 표시처럼 부가적인 정보를 전달할 때 유용하답니다.

출처: 문서

본문

Usage guidelines (사용 지침)

  • 보조 상태 표시에 배지를 사용하세요: 기존 컨트롤이나 항목을 업데이트하는 짧은 개수나 컴팩트한 상태(예: 받은 편지함 버튼의 안 읽은 메시지 수)에 배지를 사용해요. 상태가 그 자체로 중요하다면 배지만에 의존하지 말고 UI에 직접 표시해요.
  • 배지를 소유한 요소에 라벨을 지정하세요: 배지는 다른 요소에 묶인 시각적 단서이므로, 그 의미는 해당 요소의 접근 가능한 이름(accessible name)의 일부가 되어야 해요. 예를 들어 대상 요소에 aria-label="Inbox" 대신 aria-label="Inbox, 4 unread messages"를 사용해요.
  • 단순 상태에는 점 배지를 사용하세요: 점 배지(dot badge)는 텍스트나 숫자를 표시하지 않으므로, 주변 UI가 상태(예: Online 또는 Unread)를 명확하게 만드는 경우에만 사용해요.
import IconButton from '@mui/material/IconButton';
import Badge from '@mui/material/Badge';
import MailIcon from '@mui/icons-material/Mail';

const maxVisibleNotifications = 99;
const unreadNotificationsCount = 100;

function getUnreadNotificationsLabel(count: number) {
  if (count === 0) {
    return 'show no unread notifications';
  }
  if (count > maxVisibleNotifications) {
    return `show more than ${maxVisibleNotifications} unread notifications`;
  }
  return `show ${count} unread notification${count === 1 ? '' : 's'}`;
}

export default function BadgeIntro() {
  const label = getUnreadNotificationsLabel(unreadNotificationsCount);

  return (
    <IconButton aria-label={label}>
      <Badge
        badgeContent={unreadNotificationsCount}
        color="secondary"
        max={maxVisibleNotifications}
      >
        <MailIcon />
      </Badge>
    </IconButton>
  );
}

이 데모는 Badge가 있는 ListItemButton에 동일한 패턴을 적용해요: 보이는 개수를 항목의 접근 가능한 이름에 포함시켜 주변 UI 맥락에서 발음되도록 해요.

import Badge from '@mui/material/Badge';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import Paper from '@mui/material/Paper';
import DraftsIcon from '@mui/icons-material/Drafts';
import InboxIcon from '@mui/icons-material/Inbox';
import SendIcon from '@mui/icons-material/Send';

const unreadMessagesCount = 4;

export default function BadgeListItem() {
  return (
    <Paper variant="outlined" sx={{ width: 320, maxWidth: '100%' }}>
      <List component="nav" aria-label="mail folders" sx={{ py: 0 }}>
        <ListItemButton
          selected
          aria-current="page"
          aria-label={`Inbox, ${unreadMessagesCount} unread messages`}
        >
          <ListItemIcon>
            <Badge badgeContent={unreadMessagesCount} color="primary">
              <InboxIcon />
            </Badge>
          </ListItemIcon>
          <ListItemText primary="Inbox" />
        </ListItemButton>
        <ListItemButton>
          <ListItemIcon>
            <SendIcon />
          </ListItemIcon>
          <ListItemText primary="Sent" />
        </ListItemButton>
        <ListItemButton>
          <ListItemIcon>
            <DraftsIcon />
          </ListItemIcon>
          <ListItemText primary="Drafts" />
        </ListItemButton>
      </List>
    </Paper>
  );
}

Badge content (배지 내용)

badgeContent를 사용해서 감싼 요소에 짧은 개수 또는 라벨을 추가해요.

import Badge from '@mui/material/Badge';
import IconButton from '@mui/material/IconButton';
import MailIcon from '@mui/icons-material/Mail';

export default function SimpleBadge() {
  return (
    <IconButton aria-label="show 4 unread messages">
      <Badge badgeContent={4} color="primary">
        <MailIcon />
      </Badge>
    </IconButton>
  );
}

Dot badge (점 배지)

개수 없이 컴팩트한 상태 표시를 하려면 variant="dot"을 사용해요.

import IconButton from '@mui/material/IconButton';
import Badge from '@mui/material/Badge';
import NotificationsIcon from '@mui/icons-material/Notifications';

export default function DotBadge() {
  return (
    <IconButton aria-label="show new notifications">
      <Badge color="secondary" variant="dot">
        <NotificationsIcon />
      </Badge>
    </IconButton>
  );
}

Visibility (가시성)

invisible prop으로 배지 가시성을 제어해요.

import * as React from 'react';
import Stack from '@mui/material/Stack';
import Badge from '@mui/material/Badge';
import IconButton from '@mui/material/IconButton';
import Switch from '@mui/material/Switch';
import FormControlLabel from '@mui/material/FormControlLabel';
import MailIcon from '@mui/icons-material/Mail';

const unreadMessagesCount = 4;

export default function BadgeVisibility() {
  const [invisible, setInvisible] = React.useState(false);

  const handleBadgeVisibility = () => {
    setInvisible((previousInvisible) => !previousInvisible);
  };

  return (
    <Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
      <IconButton
        aria-label={
          invisible
            ? 'open inbox'
            : `open inbox, ${unreadMessagesCount} unread messages`
        }
      >
        <Badge
          color="secondary"
          badgeContent={unreadMessagesCount}
          invisible={invisible}
        >
          <MailIcon />
        </Badge>
      </IconButton>
      <FormControlLabel
        control={<Switch checked={!invisible} onChange={handleBadgeVisibility} />}
        label="Show unread count"
      />
    </Stack>
  );
}

badgeContent가 0이면 배지가 자동으로 숨겨져요. 인터페이스에서 0이 의미 있을 때는 showZero prop으로 이를 오버라이드해요.

import Stack from '@mui/material/Stack';
import Badge from '@mui/material/Badge';
import IconButton from '@mui/material/IconButton';
import MailIcon from '@mui/icons-material/Mail';

export default function ShowZeroBadge() {
  return (
    <Stack spacing={2} direction="row">
      <IconButton aria-label="show no unread messages">
        <Badge color="secondary" badgeContent={0}>
          <MailIcon />
        </Badge>
      </IconButton>
      <IconButton aria-label="show 0 unread messages">
        <Badge color="secondary" badgeContent={0} showZero>
          <MailIcon />
        </Badge>
      </IconButton>
    </Stack>
  );
}

Maximum value (최대값)

큰 숫자 값을 상한선으로 제한하려면 max prop을 사용해요.

import Stack from '@mui/material/Stack';
import Badge from '@mui/material/Badge';
import IconButton from '@mui/material/IconButton';
import MailIcon from '@mui/icons-material/Mail';

export default function BadgeMax() {
  return (
    <Stack spacing={2} direction="row">
      <IconButton aria-label="show 99 unread messages">
        <Badge color="secondary" badgeContent={99}>
          <MailIcon />
        </Badge>
      </IconButton>
      <IconButton aria-label="show more than 99 unread messages">
        <Badge color="secondary" badgeContent={100}>
          <MailIcon />
        </Badge>
      </IconButton>
      <IconButton aria-label="show more than 999 unread messages">
        <Badge color="secondary" badgeContent={1000} max={999}>
          <MailIcon />
        </Badge>
      </IconButton>
    </Stack>
  );
}

Customization (커스터마이징)

Color (색상)

color prop을 사용해서 테마 팔레트 색상을 배지에 적용해요.

import Badge from '@mui/material/Badge';
import Stack from '@mui/material/Stack';
import IconButton from '@mui/material/IconButton';
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';

export default function ColorBadge() {
  return (
    <Stack spacing={2} direction="row">
      <IconButton aria-label="show 8 shared files">
        <Badge badgeContent={8} color="primary">
          <FolderOpenIcon />
        </Badge>
      </IconButton>
      <IconButton aria-label="show 2 confirmed events">
        <Badge badgeContent={2} color="success">
          <CalendarMonthIcon />
        </Badge>
      </IconButton>
      <IconButton aria-label="show 1 critical alert">
        <Badge badgeContent={1} color="error">
          <ReportProblemOutlinedIcon />
        </Badge>
      </IconButton>
    </Stack>
  );
}

Badge alignment (배지 정렬)

anchorOrigin prop을 사용해서 배지를 감싼 요소의 어느 모서리로든 이동할 수 있어요.

import * as React from 'react';
import Badge from '@mui/material/Badge';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import MailIcon from '@mui/icons-material/Mail';
import { HighlightedCode } from '@mui/internal-core-docs/HighlightedCode';

type BadgeVerticalOrigin = 'top' | 'bottom';
type BadgeHorizontalOrigin = 'right' | 'left';

export default function BadgeAlignment() {
  const [horizontal, setHorizontal] = React.useState<BadgeHorizontalOrigin>('right');
  const [vertical, setVertical] = React.useState<BadgeVerticalOrigin>('top');

  const handleHorizontalChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setHorizontal(event.target.value as BadgeHorizontalOrigin);
  };

  const handleVerticalChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setVertical(event.target.value as BadgeVerticalOrigin);
  };

  const jsx = `
<IconButton aria-label="show 12 unread messages">
  <Badge
    badgeContent={12}
    color="secondary"
    anchorOrigin={{
      vertical: '${vertical}',
      horizontal: '${horizontal}',
    }}
  >
    <MailIcon />
  </Badge>
</IconButton>
`;

  return (
    <Box sx={{ width: '100%' }}>
      <Box
        sx={{
          display: 'flex',
          justifyContent: 'center',
          '& fieldset': {
            margin: 3,
          },
        }}
      >
        <FormControl component="fieldset">
          <FormLabel component="legend">Vertical</FormLabel>
          <RadioGroup
            name="vertical"
            value={vertical}
            onChange={handleVerticalChange}
          >
            <FormControlLabel value="top" control={<Radio />} label="Top" />
            <FormControlLabel value="bottom" control={<Radio />} label="Bottom" />
          </RadioGroup>
        </FormControl>
        <FormControl component="fieldset">
          <FormLabel component="legend">Horizontal</FormLabel>
          <RadioGroup
            name="horizontal"
            value={horizontal}
            onChange={handleHorizontalChange}
          >
            <FormControlLabel value="right" control={<Radio />} label="Right" />
            <FormControlLabel value="left" control={<Radio />} label="Left" />
          </RadioGroup>
        </FormControl>
      </Box>
      <Box
        sx={{
          display: 'flex',
          justifyContent: 'center',
          color: 'action.active',
          '& > *': {
            margin: 2,
          },
        }}
      >
        <IconButton aria-label="show unread messages">
          <Badge
            color="secondary"
            variant="dot"
            anchorOrigin={{
              horizontal,
              vertical,
            }}
          >
            <MailIcon />
          </Badge>
        </IconButton>
        <IconButton aria-label="show 1 unread message">
          <Badge
            color="secondary"
            badgeContent={1}
            anchorOrigin={{
              horizontal,
              vertical,
            }}
          >
            <MailIcon />
          </Badge>
        </IconButton>
        <IconButton aria-label="show 12 unread messages">
          <Badge
            color="secondary"
            badgeContent={12}
            anchorOrigin={{
              horizontal,
              vertical,
            }}
          >
            <MailIcon />
          </Badge>
        </IconButton>
        <IconButton aria-label="show more than 99 unread messages">
          <Badge
            color="secondary"
            badgeContent={123}
            anchorOrigin={{
              horizontal,
              vertical,
            }}
          >
            <MailIcon />
          </Badge>
        </IconButton>
        <IconButton aria-label="show more than 999 unread messages">
          <Badge
            color="secondary"
            max={999}
            badgeContent={1337}
            anchorOrigin={{
              horizontal,
              vertical,
            }}
          >
            <MailIcon />
          </Badge>
        </IconButton>
      </Box>
      <HighlightedCode code={jsx} language="jsx" />
    </Box>
  );
}

Badge overlap (배지 겹침)

감싼 요소가 원형(circular)일 때 overlap prop을 사용해요.

import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Badge from '@mui/material/Badge';

const shapeSize = 32;

export default function BadgeOverlap() {
  return (
    <Stack spacing={3} direction="row">
      <Badge color="secondary" badgeContent={1}>
        <Rectangle />
      </Badge>
      <Badge color="secondary" variant="dot">
        <Rectangle />
      </Badge>
      <Badge color="secondary" overlap="circular" badgeContent={1}>
        <Circle />
      </Badge>
      <Badge color="secondary" overlap="circular" variant="dot">
        <Circle />
      </Badge>
    </Stack>
  );
}

function Rectangle() {
  return (
    <Box
      component="span"
      sx={{ bgcolor: 'primary.main', width: shapeSize, height: shapeSize }}
    />
  );
}

function Circle() {
  return (
    <Box
      component="span"
      sx={{
        bgcolor: 'primary.main',
        width: shapeSize,
        height: shapeSize,
        borderRadius: '50%',
      }}
    />
  );
}

Custom styles (커스텀 스타일)

테마 스타일 오버라이드, sx prop, 또는 styled()을 사용해서 배지를 커스터마이즈할 수 있어요. 자세한 내용은 커스터마이징 가이드에서 확인해요.

import Badge from '@mui/material/Badge';
import Avatar from '@mui/material/Avatar';
import List from '@mui/material/List';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';

type ContactStatus = 'online' | 'offline';

const statusLabels: Record<ContactStatus, string> = {
  online: 'Online',
  offline: 'Offline',
};

const contacts = [
  { name: 'Remy Sharp', initials: 'R', status: 'online' },
  { name: 'Travis Howard', initials: 'T', status: 'offline' },
  { name: 'Cindy Baker', initials: 'C', status: 'online' },
] as const;

const ContactStatusBadge = styled(Badge, {
  shouldForwardProp: (prop) => prop !== 'status',
})<{ status: ContactStatus }>(({ theme, status }) => {
  const themePalette = (theme.vars ?? theme).palette;
  const offlineBadgeColor = theme.vars
    ? theme.vars.palette.Avatar.defaultBg
    : theme.palette.grey[400];

  return {
    '& .MuiBadge-badge': {
      height: 10,
      minWidth: 10,
      border: `1px solid ${themePalette.grey[300]}`,
      boxShadow: `0 0 0 2px ${themePalette.background.paper}`,
      ...(status === 'offline' && {
        backgroundColor: offlineBadgeColor,
        ...(theme.vars
          ? {}
          : theme.applyStyles('dark', {
              backgroundColor: theme.palette.grey[600],
            })),
      }),
    },
  };
});

export default function CustomizedBadges() {
  return (
    <Paper variant="outlined" sx={{ width: 320, maxWidth: '100%' }}>
      <List component="nav" aria-label="contacts" sx={{ py: 0 }}>
        {contacts.map((contact) => {
          const statusLabel = statusLabels[contact.status];

          return (
            <ListItemButton
              key={contact.name}
              aria-label={`${contact.name}, ${contact.status}`}
            >
              <ListItemAvatar>
                <ContactStatusBadge
                  status={contact.status}
                  color={contact.status === 'online' ? 'success' : 'default'}
                  variant="dot"
                  overlap="circular"
                  anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
                >
                  <Avatar>{contact.initials}</Avatar>
                </ContactStatusBadge>
              </ListItemAvatar>
              <ListItemText primary={contact.name} secondary={statusLabel} />
            </ListItemButton>
          );
        })}
      </List>
    </Paper>
  );
}

Badge API (Badge API)

Demos (데모)

이 React 컴포넌트 사용에 대한 예제와 자세한 내용은 컴포넌트 데모 페이지를 방문해보세요:

Import (불러오기)

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

Props (속성)

Name Type Default Required Description
anchorOrigin { horizontal?: 'left' | 'right', vertical?: 'bottom' | 'top' } {\n vertical: 'top',\n horizontal: 'right',\n} No
badgeContent node - No
children node - No
classes object - No Override or extend the styles applied to the component.
color 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' | string 'default' No
component elementType - No
invisible bool false No
max number 99 No
overlap 'circular' | 'rectangular' 'rectangular' No
showZero bool false No
slotProps { badge?: func | object, root?: func | object } {} No
slots { badge?: elementType, root?: elementType } {} No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
variant 'dot' | 'standard' | string 'standard' No

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

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

Theme default props (테마 기본 props)

MuiBadge을 사용해서 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.

Slots (슬롯)

Name Default Class Description
root span .MuiBadge-root The component that renders the root.
badge span .MuiBadge-badge The component that renders the badge.

CSS

Rule name (규칙 이름)

Global class Rule name Description
- anchorOriginBottomLeft Styles applied to the badge span element if anchorOrigin={{ 'bottom', 'left' }}.
- anchorOriginBottomLeftCircular Styles applied to the badge span element if anchorOrigin={{ 'bottom', 'left' }} overlap="circular".
- anchorOriginBottomLeftRectangular Styles applied to the badge span element if anchorOrigin={{ 'bottom', 'left' }} overlap="rectangular".
- anchorOriginBottomRight Styles applied to the badge span element if anchorOrigin={{ 'bottom', 'right' }}.
- anchorOriginBottomRightCircular Styles applied to the badge span element if anchorOrigin={{ 'bottom', 'right' }} overlap="circular".
- anchorOriginBottomRightRectangular Styles applied to the badge span element if anchorOrigin={{ 'bottom', 'right' }} overlap="rectangular".
- anchorOriginTopLeft Styles applied to the badge span element if anchorOrigin={{ 'top', 'left' }}.
- anchorOriginTopLeftCircular Styles applied to the badge span element if anchorOrigin={{ 'top', 'left' }} overlap="circular".
- anchorOriginTopLeftRectangular Styles applied to the badge span element if anchorOrigin={{ 'top', 'left' }} overlap="rectangular".
- anchorOriginTopRight Styles applied to the badge span element if anchorOrigin={{ 'top', 'right' }}.
- anchorOriginTopRightCircular Styles applied to the badge span element if anchorOrigin={{ 'top', 'right' }} overlap="circular".
- anchorOriginTopRightRectangular Styles applied to the badge span element if anchorOrigin={{ 'top', 'right' }} overlap="rectangular".
- colorError Styles applied to the badge span element if color="error".
- colorInfo Styles applied to the badge span element if color="info".
- colorPrimary Styles applied to the badge span element if color="primary".
- colorSecondary Styles applied to the badge span element if color="secondary".
- colorSuccess Styles applied to the badge span element if color="success".
- colorWarning Styles applied to the badge span element if color="warning".
- dot Styles applied to the badge span element if variant="dot".
- invisible State class applied to the badge span element if invisible={true}.
- overlapCircular Styles applied to the badge span element if overlap="circular".
- overlapRectangular Styles applied to the badge span element if overlap="rectangular".
- standard Styles applied to the badge span element if variant="standard".

Source code (소스 코드)

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

더 알아보기 (Learn more)