Bottom sheet

사용자가 현재 맥락과 밀접한 관련이 있는 범위가 정해진 작업을 수행할 수 있도록 돕는 요소입니다. 모바일 화면 하단에 오버레이되어 추가적인 사항이나 액션을 표시합니다. 메인 콘텐츠에 대한 접근성을 유지하면서 임시적으로 간단한 정보를 쉽게 접근할 수 있게 해줍니다.

Basic Bottom sheet

Bottom sheet를 표시합니다.

breakpoint에 따라 variant를 override 하여 Popup 과 쉽게 통합할 수 있습니다.

import {
  Button,
  Modal,
  ActionArea,
  ActionAreaButton,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalNavigation,
  ModalSummary,
  ModalTrigger,
} from '@wanteddev/wds';

const Demo = () => {
  return (
    <Modal>
      <ModalTrigger>
        <Button>Open</Button>
      </ModalTrigger>

      <ModalContainer variant="bottom">
        <ModalContent>
          <ModalContentItem>
            <ModalHeading>Heading</ModalHeading>
            <ModalSummary>Summary</ModalSummary>
            <ModalDescription>Description</ModalDescription>
          </ModalContentItem>
        </ModalContent>

        <ActionArea>
          <ActionAreaButton>
            Main action
          </ActionAreaButton>
        </ActionArea>
      </ModalContainer>
    </Modal>
  )
}

export default Demo;

Anatomy

Bottom sheet 는 여러 컴포넌트를 조합해서 사용합니다.

ModalTrigger, ModalNavigation 은 선택적으로 사용할 수 있습니다.

기본 구성은 아래와 같습니다.

<Modal>
  <ModalTrigger>
    <Button />
  </ModalTrigger>

  <ModalContainer>
    <ModalNavigation />

    <ModalContent>
      <ModalContentItem>
        <ModalHeading />
        <ModalSummary />
        <ModalDescription />
      </ModalContentItem>
    </ModalContent>

    <ActionArea>
      <ActionAreaButton />
    </ActionArea>
  </ModalContainer>
</Modal>

Sizes

1가지 사이즈만 사용합니다.

  • medium
import {
  FlexBox,
  Button,
  Modal,
  ActionArea,
  ActionAreaButton,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalNavigation,
  ModalSummary,
  ModalTrigger,
} from '@wanteddev/wds';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexWrap="wrap">
      <Modal>
        <ModalTrigger>
          <Button>Medium</Button>
        </ModalTrigger>

        <ModalContainer variant="bottom" size="medium">
          <ModalContent>
            <ModalContentItem>
              <ModalHeading>Heading</ModalHeading>
              <ModalSummary>Summary</ModalSummary>
              <ModalDescription>Description</ModalDescription>
            </ModalContentItem>
          </ModalContent>

          <ActionArea>
            <ActionAreaButton>
              Main action
            </ActionAreaButton>
          </ActionArea>
        </ModalContainer>
      </Modal>
    </FlexBox>
  )
}

export default Demo;

Resize

resize 옵션을 사용하면 Bottom sheet가 차지하는 높이를 조정할 수 있습니다.

  • hug (default)
  • fixed
import {
  FlexBox,
  Button,
  Modal,
  ActionArea,
  ActionAreaButton,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalNavigation,
  ModalSummary,
  ModalTrigger,
} from '@wanteddev/wds';

const Demo = () => {
  return (
    <FlexBox gap="12px" flexWrap="wrap">
      <Modal>
        <ModalTrigger>
          <Button>Hug</Button>
        </ModalTrigger>

        <ModalContainer variant="bottom" resize="hug">
          <ModalContent>
            <ModalContentItem>
              <ModalHeading>Heading</ModalHeading>
              <ModalSummary>Summary</ModalSummary>
              <ModalDescription>Description</ModalDescription>
            </ModalContentItem>
          </ModalContent>

          <ActionArea>
            <ActionAreaButton>
              Main action
            </ActionAreaButton>
          </ActionArea>
        </ModalContainer>
      </Modal>

      <Modal>
        <ModalTrigger>
          <Button>Fixed</Button>
        </ModalTrigger>

        <ModalContainer variant="bottom" resize="fixed">
          <ModalNavigation>
            Fixed
          </ModalNavigation>

          <ModalContent>
            <ModalContentItem>
              <ModalHeading>Heading</ModalHeading>
              <ModalSummary>Summary</ModalSummary>
              <ModalDescription>Description</ModalDescription>
            </ModalContentItem>
          </ModalContent>

          <ActionArea>
            <ActionAreaButton>
              Main action
            </ActionAreaButton>
          </ActionArea>
        </ModalContainer>
      </Modal>
    </FlexBox>
  )
}

export default Demo;

Handle

Bottom sheet의 상단에 핸들을 표시하여 드래그로 숨기고 열 수 있는 옵션을 사용할 수 있습니다.

  • true
  • false (default)

handle이 활성화된 경우 dimmer 영역 클릭 시 닫히지 않고 아래로 숨겨집니다.

이 때 숨겨지는 영역의 높이는 ModalNavigation의 높이를 사용합니다.

import {
  FlexBox,
  Button,
  Modal,
  ActionArea,
  ActionAreaButton,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalNavigation,
  ModalSummary,
  ModalTrigger,
} from '@wanteddev/wds';

const Demo = () => {
  return (
    <Modal>
      <ModalTrigger>
        <Button>Handle</Button>
      </ModalTrigger>

      <ModalContainer variant="bottom" handle>
        <ModalNavigation>
          Handle
        </ModalNavigation>

        <ModalContent>
          <ModalContentItem>
            <ModalHeading>Heading</ModalHeading>
            <ModalSummary>Summary</ModalSummary>
            <ModalDescription>Description</ModalDescription>
          </ModalContentItem>
        </ModalContent>

        <ActionArea>
          <ActionAreaButton>
            Main action
          </ActionAreaButton>
        </ActionArea>
      </ModalContainer>
    </Modal>
  )
}

export default Demo;

Peek height

peekHeight 옵션을 사용하면 숨겨지는 영역의 높이를 조정할 수 있습니다.

import {
  FlexBox,
  Button,
  Modal,
  ActionArea,
  ActionAreaButton,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalSummary,
  ModalTrigger,
} from '@wanteddev/wds';
import { useState } from 'react';

const Demo = () => {
  const [open, setOpen] = useState(false);

  return (
    <Modal open={open} onOpenChange={setOpen}>
      <ModalTrigger>
        <Button>Peek height</Button>
      </ModalTrigger>

      <ModalContainer variant="bottom" handle peekHeight={76}>
        <ModalContent>
          <ModalContentItem>
            <ModalHeading>Heading</ModalHeading>
            <ModalSummary>Summary</ModalSummary>
            <ModalDescription>Description</ModalDescription>
          </ModalContentItem>
        </ModalContent>

        <ActionArea>
          <ActionAreaButton onClick={() => setOpen(false)} variant="alternative">
            Close sheet
          </ActionAreaButton>
        </ActionArea>
      </ModalContainer>
    </Modal>
  )
}

export default Demo;

Visibility

아래로 숨겨지거나 다시 나타날 때 onVisibilityChange 콜백을 사용할 수 있습니다.

해당 콜백을 사용하여 아래로 숨김 처리 되지 않고 바로 닫히게 구성할 수 있습니다.

import { useState } from 'react';
import {
  Button,
  Modal,
  ActionArea,
  ActionAreaButton,
  ModalTrigger,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalSummary,
} from '@wanteddev/wds';

const Demo = () => {
  const [open, setOpen] = useState(false);

  return (
    <Modal
      open={open}
      onOpenChange={setOpen}
      onVisibilityChange={(visibility) => {
        if (visibility === 'hidden') {
          setOpen(false)
        }
      }}
    >
      <ModalTrigger>
        <Button>Visibility</Button>
      </ModalTrigger>

      <ModalContainer variant="bottom" handle peekHeight={76}>
        <ModalContent>
          <ModalContentItem>
            <ModalHeading>Heading</ModalHeading>
            <ModalSummary>Summary</ModalSummary>
            <ModalDescription>Description</ModalDescription>
          </ModalContentItem>
        </ModalContent>

        <ActionArea variant="cancel">
          <ActionAreaButton onClick={() => setOpen(false)}>
            Close sheet
          </ActionAreaButton>
        </ActionArea>
      </ModalContainer>
    </Modal>
  )
}

export default Demo;

Full modal

variant="full" 옵션을 사용하면 전체 화면 모달 형태로 표시할 수 있습니다.

Popup Navigation 과 동일하게 Navigation을 사용할 수 있습니다.

import {
  Button,
  Modal,
  ModalTrigger,
  ActionArea,
  ActionAreaButton,
  ModalContainer,
  ModalContent,
  ModalContentItem,
  ModalDescription,
  ModalHeading,
  ModalNavigation,
  ModalSummary,
} from '@wanteddev/wds';

const Demo = () => {

  return (
    <Modal>
       <ModalTrigger>
        <Button>Full modal</Button>
      </ModalTrigger>

      <ModalContainer variant="full">
        <ModalNavigation>
          Title
        </ModalNavigation>

        <ModalContent>
          <ModalContentItem>
            <ModalHeading>Heading</ModalHeading>
            <ModalSummary>Summary</ModalSummary>
            <ModalDescription>Description</ModalDescription>
          </ModalContentItem>
        </ModalContent>

        <ActionArea>
          <ActionAreaButton>
            Main action
          </ActionAreaButton>
        </ActionArea>
      </ModalContainer>
    </Modal>
  )
}

export default Demo;

API

NameTypesdefaultValue
open
boolean
-
defaultOpen
boolean
-
onOpenChange
(open: boolean) => void
-
onVisibilityChange
(visibility: "hidden" | "visible") => void
-
children
ReactNode
-
sx
SxProp
-

ModalContainer

NameTypesdefaultValue
as
ElementType
-
variant
"bottom" | "popup" | "full"
"popup"
handle
boolean
-
peekHeight
number
-
sticky
boolean
true
size
"small" | "medium" | "large" | "xlarge"
"medium"
resize
"fixed" | "hug"
"hug"
children
ReactNode
-
wrapperProps
DefaultComponentProps<{}, 'div'>
-
dimmer
ReactNode
<ModalDimmer />
container
null | Element | DocumentFragment
-
disableOutsideClickClose
boolean
false
disableEscapeKeyDownClose
boolean
false
disableRemoveScroll
boolean
false
disableFocusScope
boolean
false
disableAriaHiddenOthers
boolean
false
disablePortal
boolean
false
forceMount
boolean
false
sx
SxProp
-
xl
Merge<Pick<ModalContainerDefaultProps, "variant" | "size" | "handle" | "resize">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<ModalContainerDefaultProps, "variant" | "size" | "handle" | "resize">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<ModalContainerDefaultProps, "variant" | "size" | "handle" | "resize">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<ModalContainerDefaultProps, "variant" | "size" | "handle" | "resize">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<ModalContainerDefaultProps, "variant" | "size" | "handle" | "resize">, { sx?: CSSInterpolation; }> | undefined
-

ModalNavigation

NameTypesdefaultValue
variant
"search" | "display" | "normal" | "floating" | "emphasized"
-
leadingContent
ReactNode
-
trailingContent
ReactNode
<ModalClose />
children
ReactNode
-
sx
SxProp
-
xl
{ sx?: CSSInterpolation; } | undefined
-
lg
{ sx?: CSSInterpolation; } | undefined
-
md
{ sx?: CSSInterpolation; } | undefined
-
sm
{ sx?: CSSInterpolation; } | undefined
-
xs
{ sx?: CSSInterpolation; } | undefined
-
background
boolean
-
toolbar
ReactNode
-
titleId
string
-

ModalNavigationButton

TopNavigationButton 과 동일한 Props를 사용합니다.

NameTypesdefaultValue
as
E
-
variant
"text" | "icon"
-
color
"primary" | "assistive"
-
disabled
boolean
-
size
number | "small" | "medium"
-
children
ReactNode
-
sx
SxProp
-

ModalClose

TopNavigationButton 과 동일한 Props를 사용합니다.

NameTypesdefaultValue
as
E
-
variant
"text" | "icon"
-
color
"primary" | "assistive"
-
disabled
boolean
-
size
number | "small" | "medium"
-
children
ReactNode
-
sx
SxProp
-

ModalContent

NameTypesdefaultValue
gap
Property.Gap<string | number> | undefined
"calc(var(--wds-modal-content-margin, 20px))"
children
ReactNode
-
sx
SxProp
-
xl
Merge<Pick<ModalContentDefaultProps, "gap">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<ModalContentDefaultProps, "gap">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<ModalContentDefaultProps, "gap">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<ModalContentDefaultProps, "gap">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<ModalContentDefaultProps, "gap">, { sx?: CSSInterpolation; }> | undefined
-

ModalContentItem

FlexBox 와 동일한 Props를 사용합니다.

ModalHeading

Typography 와 동일한 Props를 사용합니다.

ModalSummary

Typography 와 동일한 Props를 사용합니다.

ModalDescription

Typography 와 동일한 Props를 사용합니다.

© 2026 Wanted Lab, Inc.