Dialog

Dialog (다이얼로그)

사용자에게 중요한 정보를 전달하거나 결정을 요구하거나 여러 작업을 묶어 수행하게 하는 다이얼로그 컴포넌트를 알아볼게요. 모달 창 위에 떠서 앱을 잠시 멈추는 방식이라, 꼭 필요할 때만 사용해야 하는 컴포넌트예요.

출처: 문서

본문

다이얼로그는 사용자에게 작업에 대해 알리고, 중요한 정보를 포함하거나, 결정을 요구하거나, 여러 작업을 수반할 수 있어요.

다이얼로그는 앱 콘텐츠 앞에 나타나 중요한 정보를 제공하거나 결정을 요청하는 modal 창의 한 유형이에요. 다이얼로그는 나타나면 모든 앱 기능을 비활성화하고, 확인되거나 닫히거나 필요한 작업이 수행될 때까지 화면에 남아 있어요.

다이얼로그는 의도적으로 방해적(interruptive)이기 때문에, 드물게 사용해야 해요.

소개 (Introduction)

다이얼로그는 관련 컴포넌트 모음으로 구현돼요:

  • Dialog: 모달을 렌더링하는 부모 컴포넌트.
  • Dialog Title: 다이얼로그의 제목에 사용되는 래퍼.
  • Dialog Actions: 다이얼로그의 버튼을 위한 선택적 컨테이너.
  • Dialog Content: 다이얼로그의 콘텐츠를 표시하기 위한 선택적 컨테이너.
  • Dialog Content Text: <DialogContent /> 안의 텍스트용 래퍼.
  • Slide: 선택적 Transition으로, 화면 가장자리에서 다이얼로그를 슬라이드 인되게 해요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Avatar from '@mui/material/Avatar';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import DialogTitle from '@mui/material/DialogTitle';
import Dialog from '@mui/material/Dialog';
import PersonIcon from '@mui/icons-material/Person';
import AddIcon from '@mui/icons-material/Add';
import Typography from '@mui/material/Typography';
import { blue } from '@mui/material/colors';

const emails = ['[email protected]', '[email protected]'];

export interface SimpleDialogProps {
  open: boolean;
  selectedValue: string;
  onClose: (value: string) => void;
}

function SimpleDialog(props: SimpleDialogProps) {
  const { onClose, selectedValue, open } = props;

  const handleClose = () => {
    onClose(selectedValue);
  };

  const handleListItemClick = (value: string) => {
    onClose(value);
  };

  return (
    <Dialog onClose={handleClose} open={open}>
      <DialogTitle>Set backup account</DialogTitle>
      <List sx={{ pt: 0 }}>
        {emails.map((email) => (
          <ListItem disablePadding key={email}>
            <ListItemButton onClick={() => handleListItemClick(email)}>
              <ListItemAvatar>
                <Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
                  <PersonIcon />
                </Avatar>
              </ListItemAvatar>
              <ListItemText primary={email} />
            </ListItemButton>
          </ListItem>
        ))}
        <ListItem disablePadding>
          <ListItemButton
            autoFocus
            onClick={() => handleListItemClick('addAccount')}
          >
            <ListItemAvatar>
              <Avatar>
                <AddIcon />
              </Avatar>
            </ListItemAvatar>
            <ListItemText primary="Add account" />
          </ListItemButton>
        </ListItem>
      </List>
    </Dialog>
  );
}

export default function SimpleDialogDemo() {
  const [open, setOpen] = React.useState(false);
  const [selectedValue, setSelectedValue] = React.useState(emails[1]);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = (value: string) => {
    setOpen(false);
    setSelectedValue(value);
  };

  return (
    <div>
      <Typography variant="subtitle1" component="div">
        Selected: {selectedValue}
      </Typography>
      <br />
      <Button variant="outlined" onClick={handleClickOpen}>
        Open simple dialog
      </Button>
      <SimpleDialog
        selectedValue={selectedValue}
        open={open}
        onClose={handleClose}
      />
    </div>
  );
}

기본 (Basics)

import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';

알림 (Alerts)

알림은 사용자에게 상황을 알려주는, 확인이 필요한 긴급한 중단(interruption)이에요.

role="alertdialog"를 사용해서 Alert Dialog를 만들어요. 이렇게 하면 보조 기술(assistive technologies)에 다이얼로그의 올바른 목적을 제공해요.

대부분의 알림은 제목이 필요하지 않아요. 다음 중 하나로 결정을 한두 문장으로 요약해요:

  • 질문하기(예: "이 대화를 삭제할까요?")
  • 동작 버튼과 관련된 진술하기

제목 표시줄 알림은 잠재적인 연결 손실 같은 고위험 상황에서만 사용하세요. 사용자는 제목과 버튼 텍스트만으로 선택을 이해할 수 있어야 해요.

제목이 필요한 경우:

  • 콘텐츠 영역에 설명이 포함된 명확한 질문이나 진술을 사용하세요, 예를 들면 "USB 저장소를 지울까요?".
  • "경고!"나 "정말 확실한가요?" 같은 사과, 모호함, 질문은 피하세요.
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';

export default function AlertDialog() {
  const [open, setOpen] = React.useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open alert dialog
      </Button>
      <Dialog
        open={open}
        onClose={handleClose}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
        role="alertdialog"
      >
        <DialogTitle id="alert-dialog-title">
          {"Use Google's location service?"}
        </DialogTitle>
        <DialogContent>
          <DialogContentText id="alert-dialog-description">
            Let Google help apps determine location. This means sending anonymous
            location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose} autoFocus>
            Disagree
          </Button>
          <Button onClick={handleClose}>Agree</Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

전환 (Transitions)

slots.transition과 slotProps.transition prop으로 기본 전환을 교체할 수 있어요. 다음 예제는 Slide를 사용해요.

import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import Slide from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions';

const Transition = React.forwardRef(function Transition(
  props: TransitionProps & {
    children: React.ReactElement<any, any>;
  },
  ref: React.Ref<unknown>,
) {
  return <Slide direction="up" ref={ref} {...props} />;
});

export default function AlertDialogSlide() {
  const [open, setOpen] = React.useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Slide in alert dialog
      </Button>
      <Dialog
        open={open}
        slots={{
          transition: Transition,
        }}
        keepMounted
        onClose={handleClose}
        aria-describedby="alert-dialog-slide-description"
        role="alertdialog"
      >
        <DialogTitle>{"Use Google's location service?"}</DialogTitle>
        <DialogContent>
          <DialogContentText id="alert-dialog-slide-description">
            Let Google help apps determine location. This means sending anonymous
            location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose} autoFocus>
            Disagree
          </Button>
          <Button onClick={handleClose}>Agree</Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

폼 다이얼로그 (Form dialogs)

폼 다이얼로그는 사용자가 다이얼로그 안에서 폼 필드를 작성할 수 있게 해줘요. 예를 들어, 사이트가 잠재 구독자에게 이메일 주소를 입력하라는 안내를 한다면, 사용자는 이메일 필드를 작성하고 'Submit'을 누를 수 있어요.

import * as React from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';

export default function FormDialog() {
  const [open, setOpen] = React.useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    const formJson = Object.fromEntries((formData as any).entries());
    const email = formJson.email;
    console.log(email);
    handleClose();
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open form dialog
      </Button>
      <Dialog open={open} onClose={handleClose}>
        <DialogTitle>Subscribe</DialogTitle>
        <DialogContent>
          <DialogContentText>
            To subscribe to this website, please enter your email address here. We
            will send updates occasionally.
          </DialogContentText>
          <form onSubmit={handleSubmit} id="subscription-form">
            <TextField
              autoFocus
              required
              margin="dense"
              id="name"
              name="email"
              label="Email Address"
              type="email"
              fullWidth
              variant="standard"
            />
          </form>
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose}>Cancel</Button>
          <Button type="submit" form="subscription-form">
            Subscribe
          </Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

커스터마이즈 (Customization)

컴포넌트를 커스터마이즈하는 예제예요. 이에 대해 더 자세히 알아보려면 overrides 문서 페이지를 참고하세요.

다이얼로그에는 사용성을 돕기 위해 닫기 버튼이 추가되어 있어요.

import * as React from 'react';
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import Typography from '@mui/material/Typography';

const BootstrapDialog = styled(Dialog)(({ theme }) => ({
  '& .MuiDialogContent-root': {
    padding: theme.spacing(2),
  },
  '& .MuiDialogActions-root': {
    padding: theme.spacing(1),
  },
}));

export default function CustomizedDialogs() {
  const [open, setOpen] = React.useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };
  const handleClose = () => {
    setOpen(false);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open dialog
      </Button>
      <BootstrapDialog
        onClose={handleClose}
        aria-labelledby="customized-dialog-title"
        open={open}
      >
        <DialogTitle sx={{ m: 0, p: 2 }} id="customized-dialog-title">
          Modal title
        </DialogTitle>
        <IconButton
          aria-label="close"
          onClick={handleClose}
          sx={(theme) => ({
            position: 'absolute',
            right: 8,
            top: 8,
            color: theme.palette.grey[500],
          })}
        >
          <CloseIcon />
        </IconButton>
        <DialogContent dividers>
          <Typography gutterBottom>
            Cras mattis consectetur purus sit amet fermentum. Cras justo odio,
            dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
            consectetur ac, vestibulum at eros.
          </Typography>
          <Typography gutterBottom>
            Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
            Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
          </Typography>
          <Typography gutterBottom>
            Aenean lacinia bibendum nulla sed consectetur. Praesent commodo cursus
            magna, vel scelerisque nisl consectetur et. Donec sed odio dui. Donec
            ullamcorper nulla non metus auctor fringilla.
          </Typography>
        </DialogContent>
        <DialogActions>
          <Button autoFocus onClick={handleClose}>
            Save changes
          </Button>
        </DialogActions>
      </BootstrapDialog>
    </React.Fragment>
  );
}

전체 화면 다이얼로그 (Full-screen dialogs)

import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import ListItemText from '@mui/material/ListItemText';
import ListItemButton from '@mui/material/ListItemButton';
import List from '@mui/material/List';
import Divider from '@mui/material/Divider';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import CloseIcon from '@mui/icons-material/Close';
import Slide from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions';

const Transition = React.forwardRef(function Transition(
  props: TransitionProps & {
    children: React.ReactElement<unknown>;
  },
  ref: React.Ref<unknown>,
) {
  return <Slide direction="up" ref={ref} {...props} />;
});

export default function FullScreenDialog() {
  const [open, setOpen] = React.useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open full-screen dialog
      </Button>
      <Dialog
        fullScreen
        open={open}
        onClose={handleClose}
        slots={{
          transition: Transition,
        }}
      >
        <AppBar sx={{ position: 'relative' }}>
          <Toolbar>
            <IconButton
              edge="start"
              color="inherit"
              onClick={handleClose}
              aria-label="close"
            >
              <CloseIcon />
            </IconButton>
            <Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
              Sound
            </Typography>
            <Button autoFocus color="inherit" onClick={handleClose}>
              save
            </Button>
          </Toolbar>
        </AppBar>
        <List>
          <ListItemButton>
            <ListItemText primary="Phone ringtone" secondary="Titania" />
          </ListItemButton>
          <Divider />
          <ListItemButton>
            <ListItemText
              primary="Default notification ringtone"
              secondary="Tethys"
            />
          </ListItemButton>
        </List>
      </Dialog>
    </React.Fragment>
  );
}

선택적 크기 (Optional sizes)

fullWidth 불리언과 함께 maxWidth 열거형을 사용해서 다이얼로그의 최대 너비를 설정할 수 있어요. fullWidth prop이 true이면 다이얼로그는 maxWidth 값에 따라 적응해요.

import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Dialog, { DialogProps } from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Switch from '@mui/material/Switch';

export default function MaxWidthDialog() {
  const [open, setOpen] = React.useState(false);
  const [fullWidth, setFullWidth] = React.useState(true);
  const [maxWidth, setMaxWidth] = React.useState<DialogProps['maxWidth']>('sm');

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const handleMaxWidthChange = (event: SelectChangeEvent<typeof maxWidth>) => {
    setMaxWidth(
      // @ts-expect-error autofill of arbitrary value is not handled.
      event.target.value,
    );
  };

  const handleFullWidthChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setFullWidth(event.target.checked);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open max-width dialog
      </Button>
      <Dialog
        fullWidth={fullWidth}
        maxWidth={maxWidth}
        open={open}
        onClose={handleClose}
      >
        <DialogTitle>Optional sizes</DialogTitle>
        <DialogContent>
          <DialogContentText>
            You can set my maximum width and whether to adapt or not.
          </DialogContentText>
          <Box
            noValidate
            component="form"
            sx={{
              display: 'flex',
              flexDirection: 'column',
              m: 'auto',
              width: 'fit-content',
            }}
          >
            <FormControl sx={{ mt: 2, minWidth: 120 }}>
              <InputLabel htmlFor="max-width">maxWidth</InputLabel>
              <Select
                autoFocus
                value={maxWidth}
                onChange={handleMaxWidthChange}
                label="maxWidth"
                inputProps={{
                  name: 'max-width',
                  id: 'max-width',
                }}
              >
                <MenuItem value={false as any}>false</MenuItem>
                <MenuItem value="xs">xs</MenuItem>
                <MenuItem value="sm">sm</MenuItem>
                <MenuItem value="md">md</MenuItem>
                <MenuItem value="lg">lg</MenuItem>
                <MenuItem value="xl">xl</MenuItem>
              </Select>
            </FormControl>
            <FormControlLabel
              sx={{ mt: 1 }}
              control={
                <Switch checked={fullWidth} onChange={handleFullWidthChange} />
              }
              label="Full width"
            />
          </Box>
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose}>Close</Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

반응형 전체 화면 (Responsive full-screen)

useMediaQuery를 사용해서 다이얼로그를 반응형으로 전체 화면으로 만들 수 있어요.

import useMediaQuery from '@mui/material/useMediaQuery';

function MyComponent() {
  const theme = useTheme();
  const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

  return <Dialog fullScreen={fullScreen} />;
}
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';

export default function ResponsiveDialog() {
  const [open, setOpen] = React.useState(false);
  const theme = useTheme();
  const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open responsive dialog
      </Button>
      <Dialog
        fullScreen={fullScreen}
        open={open}
        onClose={handleClose}
        aria-labelledby="responsive-dialog-title"
      >
        <DialogTitle id="responsive-dialog-title">
          {"Use Google's location service?"}
        </DialogTitle>
        <DialogContent>
          <DialogContentText>
            Let Google help apps determine location. This means sending anonymous
            location data to Google, even when no apps are running.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button autoFocus onClick={handleClose}>
            Disagree
          </Button>
          <Button onClick={handleClose} autoFocus>
            Agree
          </Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

확인 다이얼로그 (Confirmation dialogs)

확인 다이얼로그는 옵션이 확정되기 전에 사용자가 선택을 명시적으로 확인하도록 요구해요. 예를 들어, 사용자가 여러 벨소리를 들을 수 있지만 "OK"를 눌러야만 최종 선택을 할 수 있어요.

확인 다이얼로그에서 "Cancel"을 누르면 동작이 취소되고, 변경 사항이 버려지며, 다이얼로그가 닫혀요.

import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Dialog from '@mui/material/Dialog';
import RadioGroup from '@mui/material/RadioGroup';
import Radio from '@mui/material/Radio';
import FormControlLabel from '@mui/material/FormControlLabel';

const options = [
  'None',
  'Atria',
  'Callisto',
  'Dione',
  'Ganymede',
  'Hangouts Call',
  'Luna',
  'Oberon',
  'Phobos',
  'Pyxis',
  'Sedna',
  'Titania',
  'Triton',
  'Umbriel',
];

export interface ConfirmationDialogRawProps {
  id: string;
  keepMounted: boolean;
  value: string;
  open: boolean;
  onClose: (value?: string) => void;
}

function ConfirmationDialogRaw(props: ConfirmationDialogRawProps) {
  const { onClose, value: valueProp, open, ...other } = props;
  const [value, setValue] = React.useState(valueProp);
  const radioGroupRef = React.useRef<HTMLElement>(null);

  React.useEffect(() => {
    if (!open) {
      setValue(valueProp);
    }
  }, [valueProp, open]);

  const handleEntering = () => {
    if (radioGroupRef.current != null) {
      radioGroupRef.current.focus();
    }
  };

  const handleCancel = () => {
    onClose();
  };

  const handleOk = () => {
    onClose(value);
  };

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue((event.target as HTMLInputElement).value);
  };

  return (
    <Dialog
      sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
      maxWidth="xs"
      slotProps={{
        transition: {
          onEntering: handleEntering,
        },
      }}
      open={open}
      {...other}
    >
      <DialogTitle>Phone Ringtone</DialogTitle>
      <DialogContent dividers>
        <RadioGroup
          ref={radioGroupRef}
          aria-label="ringtone"
          name="ringtone"
          value={value}
          onChange={handleChange}
        >
          {options.map((option) => (
            <FormControlLabel
              value={option}
              key={option}
              control={<Radio />}
              label={option}
            />
          ))}
        </RadioGroup>
      </DialogContent>
      <DialogActions>
        <Button autoFocus onClick={handleCancel}>
          Cancel
        </Button>
        <Button onClick={handleOk}>Ok</Button>
      </DialogActions>
    </Dialog>
  );
}

export default function ConfirmationDialog() {
  const [open, setOpen] = React.useState(false);
  const [value, setValue] = React.useState('Dione');

  const handleClickListItem = () => {
    setOpen(true);
  };

  const handleClose = (newValue?: string) => {
    setOpen(false);

    if (newValue) {
      setValue(newValue);
    }
  };

  return (
    <Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
      <List component="div" role="group">
        <ListItemButton divider disabled>
          <ListItemText primary="Interruptions" />
        </ListItemButton>
        <ListItemButton
          divider
          aria-haspopup="true"
          aria-controls="ringtone-menu"
          aria-label="phone ringtone"
          onClick={handleClickListItem}
        >
          <ListItemText primary="Phone ringtone" secondary={value} />
        </ListItemButton>
        <ListItemButton divider disabled>
          <ListItemText primary="Default notification ringtone" secondary="Tethys" />
        </ListItemButton>
        <ConfirmationDialogRaw
          id="ringtone-menu"
          keepMounted
          open={open}
          onClose={handleClose}
          value={value}
        />
      </List>
    </Box>
  );
}

비모달 다이얼로그 (Non-modal dialog)

다이얼로그는 비모달(non-modal)일 수도 있는데, 뒤에 있는 사용자 상호작용을 방해하지 않는다는 뜻이에요. 모달 대 비모달 다이얼로그 사용에 대한 더 심층적인 지침은 the Nielsen Norman Group article을 방문하세요.

아래 데모는 일반적인 비모달 다이얼로그 사용 사례인 지속형 쿠키 배너를 보여줘요.

import * as React from 'react';
import Stack from '@mui/material/Stack';
import TrapFocus from '@mui/material/Unstable_TrapFocus';
import CssBaseline from '@mui/material/CssBaseline';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Container from '@mui/material/Container';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Paper from '@mui/material/Paper';
import Fade from '@mui/material/Fade';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';

export default function CookiesBanner() {
  const [bannerOpen, setBannerOpen] = React.useState(true);

  const closeBanner = () => {
    setBannerOpen(false);
  };

  return (
    <React.Fragment>
      <CssBaseline />
      <AppBar position="fixed" component="nav">
        <Toolbar>
          <IconButton size="large" edge="start" color="inherit" aria-label="menu">
            <MenuIcon />
          </IconButton>
        </Toolbar>
      </AppBar>
      <Container component="main" sx={{ pt: 3 }}>
        <Toolbar />
        <Typography sx={{ marginBottom: 2 }}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
          tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
          enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
          imperdiet.
        </Typography>
        <Typography sx={{ marginBottom: 2 }}>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
          tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
          enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
          imperdiet.
        </Typography>
      </Container>
      <TrapFocus open disableAutoFocus disableEnforceFocus>
        <Fade appear={false} in={bannerOpen}>
          <Paper
            role="dialog"
            aria-modal="false"
            aria-label="Cookie banner"
            square
            variant="outlined"
            tabIndex={-1}
            sx={{
              position: 'fixed',
              bottom: 0,
              left: 0,
              right: 0,
              m: 0,
              p: 2,
              borderWidth: 0,
              borderTopWidth: 1,
            }}
          >
            <Stack
              direction={{ xs: 'column', sm: 'row' }}
              sx={{ justifyContent: 'space-between', gap: 2 }}
            >
              <Box
                sx={{ flexShrink: 1, alignSelf: { xs: 'flex-start', sm: 'center' } }}
              >
                <Typography sx={{ fontWeight: 'bold' }}>
                  This website uses cookies
                </Typography>
                <Typography variant="body2">
                  example.com relies on cookies to improve your experience.
                </Typography>
              </Box>
              <Stack
                direction={{
                  xs: 'row-reverse',
                  sm: 'row',
                }}
                sx={{
                  gap: 2,
                  flexShrink: 0,
                  alignSelf: { xs: 'flex-end', sm: 'center' },
                }}
              >
                <Button size="small" onClick={closeBanner} variant="contained">
                  Allow all
                </Button>
                <Button size="small" onClick={closeBanner}>
                  Reject all
                </Button>
              </Stack>
            </Stack>
          </Paper>
        </Fade>
      </TrapFocus>
    </React.Fragment>
  );
}

드래그 가능한 다이얼로그 (Draggable dialog)

react-draggable을 사용해서 드래그 가능한 다이얼로그를 만들 수 있어요. 이렇게 하려면 import한 Draggable 컴포넌트를 Dialog 컴포넌트의 PaperComponent로 전달할 수 있어요. 이렇게 하면 다이얼로그 전체가 드래그 가능해져요.

import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import Paper, { PaperProps } from '@mui/material/Paper';
import Draggable from 'react-draggable';

function PaperComponent(props: PaperProps) {
  const nodeRef = React.useRef<HTMLDivElement>(null);
  return (
    <Draggable
      nodeRef={nodeRef as React.RefObject<HTMLDivElement>}
      handle="#draggable-dialog-title"
      cancel={'[class*="MuiDialogContent-root"]'}
    >
      <Paper {...props} ref={nodeRef} />
    </Draggable>
  );
}

export default function DraggableDialog() {
  const [open, setOpen] = React.useState(false);

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <React.Fragment>
      <Button variant="outlined" onClick={handleClickOpen}>
        Open draggable dialog
      </Button>
      <Dialog
        open={open}
        onClose={handleClose}
        PaperComponent={PaperComponent}
        aria-labelledby="draggable-dialog-title"
      >
        <DialogTitle style={{ cursor: 'move' }} id="draggable-dialog-title">
          Subscribe
        </DialogTitle>
        <DialogContent>
          <DialogContentText>
            To subscribe to this website, please enter your email address here. We
            will send updates occasionally.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button autoFocus onClick={handleClose}>
            Cancel
          </Button>
          <Button onClick={handleClose}>Subscribe</Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

긴 콘텐츠 스크롤 (Scrolling long content)

다이얼로그가 사용자의 뷰포트나 기기보다 너무 길어지면 스크롤돼요.

  • scroll=paper 다이얼로그의 콘텐츠는 paper 요소 안에서 스크롤돼요.
  • scroll=body 다이얼로그의 콘텐츠는 body 요소 안에서 스크롤돼요.

무슨 말인지 아래 데모로 확인해 보세요:

import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog, { DialogProps } from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';

export default function ScrollDialog() {
  const [open, setOpen] = React.useState(false);
  const [scroll, setScroll] = React.useState<DialogProps['scroll']>('paper');

  const handleClickOpen = (scrollType: DialogProps['scroll']) => () => {
    setOpen(true);
    setScroll(scrollType);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const descriptionElementRef = React.useRef<HTMLElement>(null);
  React.useEffect(() => {
    if (open) {
      const { current: descriptionElement } = descriptionElementRef;
      if (descriptionElement !== null) {
        descriptionElement.focus();
      }
    }
  }, [open]);

  return (
    <React.Fragment>
      <Button onClick={handleClickOpen('paper')}>scroll=paper</Button>
      <Button onClick={handleClickOpen('body')}>scroll=body</Button>
      <Dialog
        open={open}
        onClose={handleClose}
        scroll={scroll}
        aria-labelledby="scroll-dialog-title"
        aria-describedby="scroll-dialog-description"
      >
        <DialogTitle id="scroll-dialog-title">Subscribe</DialogTitle>
        <DialogContent dividers={scroll === 'paper'}>
          <DialogContentText
            id="scroll-dialog-description"
            ref={descriptionElementRef}
            tabIndex={-1}
          >
            {[...new Array(50)]
              .map(
                () => `Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur et.`,
              )
              .join('\n')}
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose}>Cancel</Button>
          <Button onClick={handleClose}>Subscribe</Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}

성능 (Performance)

Modal performance 섹션을 따르세요.

제한 사항 (Limitations)

Modal limitations 섹션을 따르세요.

보조 프로젝트 (Supplementary projects)

더 고급 사용 사례를 위해 다음을 활용할 수 있어요:

material-ui-confirm

stars npm downloads

material-ui-confirm 패키지는 보일러플레이트 코드 없이 사용자 동작을 확인하는 다이얼로그를 제공해요.

접근성 (Accessibility)

Modal accessibility 섹션을 따르세요.

Dialog API

Demos (데모)

이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:

Import (임포트)

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

Props

Name Type Default Required Description
open bool - Yes
aria-describedby string - No
aria-labelledby string - No
aria-modal 'false' | 'true' | bool true No
children node - No
classes object - No Override or extend the styles applied to the component.
fullScreen bool false No
fullWidth bool false No
maxWidth 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false | string 'sm' No
onClose function(event: object, reason: string) => void - No
PaperComponent elementType Paper No
role 'alertdialog' | 'dialog' 'dialog' No
scroll 'body' | 'paper' 'paper' No
slotProps { backdrop?: func | object, container?: func | object, paper?: func | object, root?: func | object, transition?: func | object } {} No
slots { backdrop?: elementType, container?: elementType, paper?: elementType, root?: elementType, transition?: elementType } {} No
sx Array<func | object | bool> | func | object - No The system prop that allows defining system overrides as well as additional CSS styles.
transitionDuration number | { appear?: number, enter?: number, exit?: number } `{
enter: theme.transitions.duration.enteringScreen,
exit: theme.transitions.duration.leavingScreen,
}` No

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

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

Inheritance (상속)

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

Theme default props (테마 기본 props)

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

Slots

Name Default Class Description
transition Collapse - The component that renders the transition.
Follow this guide to learn more about the requirements for this component.
paper Paper .MuiDialog-paper The component that renders the paper.
container undefined .MuiDialog-container The component that renders the container.
backdrop undefined .MuiDialog-backdrop The component that renders the backdrop.
root undefined .MuiDialog-root The component that renders the root.

CSS

Rule name (규칙 이름)

Global class Rule name Description
- paperFullScreen Styles applied to the Paper component if fullScreen={true}.
- paperFullWidth Styles applied to the Paper component if fullWidth={true}.
- paperWidthFalse Styles applied to the Paper component if maxWidth=false.
- paperWidthLg Styles applied to the Paper component if maxWidth="lg".
- paperWidthMd Styles applied to the Paper component if maxWidth="md".
- paperWidthSm Styles applied to the Paper component if maxWidth="sm".
- paperWidthXl Styles applied to the Paper component if maxWidth="xl".
- paperWidthXs Styles applied to the Paper component if maxWidth="xs".
- scrollBody Styles applied to the container element if scroll="body".
- scrollPaper Styles applied to the container element if scroll="paper".

Source code (소스 코드)

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

DialogActions API

Demos (데모)

이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:

Import (임포트)

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

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
disableSpacing 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).

Theme default props (테마 기본 props)

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

CSS

Rule name (규칙 이름)

Global class Rule name Description
- root Styles applied to the root element.
- spacing Styles applied to the root element unless disableSpacing={true}.

Source code (소스 코드)

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

DialogContent API

Demos (데모)

이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:

Import (임포트)

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

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
dividers 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).

Theme default props (테마 기본 props)

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

CSS

Rule name (규칙 이름)

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

Source code (소스 코드)

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

DialogContentText API

Demos (데모)

이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:

Import (임포트)

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

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
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 (HTMLParagraphElement).

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

Inheritance (상속)

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

Theme default props (테마 기본 props)

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

CSS

Rule name (규칙 이름)

Global class Rule name Description
- root Styles applied to the root element.

Source code (소스 코드)

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

DialogTitle API

Demos (데모)

이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:

Import (임포트)

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

Props

Name Type Default Required Description
children node - No
classes object - No Override or extend the styles applied to the component.
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 (HTMLHeadingElement).

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

Inheritance (상속)

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

Theme default props (테마 기본 props)

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

CSS

Rule name (규칙 이름)

Global class Rule name Description
- root Styles applied to the root element.

Source code (소스 코드)

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

Slide API

Demos (데모)

이 React 컴포넌트 사용에 대한 예시와 세부 사항은 컴포넌트 데모 페이지를 방문하세요:

Import (임포트)

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

Props

Name Type Default Required Description
children element - Yes
addEndListener function(node: HTMLElement, done: Function) => void - No
appear bool true No
container HTML element | func - No
direction 'down' | 'left' | 'right' | 'up' 'down' No
disablePrefersReducedMotion bool false No
easing { enter?: string, exit?: string } | string `{
enter: theme.transitions.easing.easeOut,
exit: theme.transitions.easing.sharp,
}` No
in bool - No
timeout number | { appear?: number, enter?: number, exit?: number } `{
enter: theme.transitions.duration.enteringScreen,
exit: theme.transitions.duration.leavingScreen,
}` No

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

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

Inheritance (상속)

위에 명시적으로 문서화되지는 않았지만, Transition 컴포넌트의 props도 Slide에서 사용할 수 있어요. 일부 컴포넌트 하위 집합은 기본적으로 react-transition-group을 지원해요.

Source code (소스 코드)

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

더 알아보기 (Learn more)

  • Modal — 다이얼로그가 기반으로 하는 모달
  • Transitions — 전환 컴포넌트