BottomSheet
화면 하단에서 올라오는 모바일 선택 표면 — 포커스 트랩 · Escape/배경 닫기 · 핸들 드래그 다운 닫기 · 스크롤 잠금을 내장한 제어 컴포넌트입니다. Select/DatePicker의 mobile 모드가 이 컴포넌트를 합성합니다 (DK 사양 승격).
마지막 업데이트 2026-06-11
한눈에#
화면 하단에서 올라오는 모바일 선택 표면입니다 — 포커스 트랩·Escape/배경 닫기·핸들 드래그 다운 닫기·스크롤 잠금을 내장한 제어 컴포넌트입니다.
- 하단 슬라이드 업
- 핸들 드래그 다운 닫기
- auto·half·full 높이
- Select·DatePicker mobile 합성
트리거를 눌러 핸들 드래그 다운·Escape·배경 닫기를 직접 확인하세요:
사용 시점#
모바일·터치 환경에서 하단에서 올라오는 선택·작업 표면이면 BottomSheet — 측면·적응·중앙 결정은 다른 컴포넌트입니다.
엄지로 닿는 하단 영역에 옵션을 펼치고 핸들 드래그로 가볍게 닫는 모바일 선택 표면
플레이그라운드#
컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.
import { BottomSheet, Button } from '@wds/ui-web';
<BottomSheet
open={open}
onClose={() => setOpen(false)}
title="공유"
>
링크 복사 · 멤버 초대 · 내보내기
</BottomSheet>변형#
footer 슬롯이 변형 축입니다 — 취소/확인 같은 액션이 필요한 흐름은 footer를 채우고(버튼은 균등 폭으로 늘어나는 모바일 관례), 단순 정보·즉시 확정 흐름(Select mobile처럼 항목 클릭이 곧 확정)은 footer 없이 본문만 씁니다.
DK 프로젝트의 모바일 재활용 표준 표면입니다 — 선택형 오버레이 컴포넌트가
mobile prop을 받으면 팝업 대신 이 시트로 선택 UI를 렌더합니다.
Select와 DatePicker의
## 모바일 섹션을 참고하세요.
크기#
auto도 상한 85dvh를 넘지 않아 상단 컨텍스트가 항상 일부 노출됩니다. 본문이
넘치면 body 영역만 내부 스크롤되고, footer는 safe-area-inset-bottom을
반영해 홈 인디케이터를 피합니다.
표시 방식 (ADR-012 B-P2)#
variant가 표면의 성격을 가릅니다 — 기본 'modal'은 스크림으로 배경을 가리고
포커스를 가두며 배경 스크롤을 잠그는 차단형입니다(기존 동작). 'standard'는
스크림·포커스 트랩·스크롤 잠금이 없어 본문과 공존하며, 배경을 그대로 클릭할 수
있습니다(M3 standard sheet). standard는 배경 클릭으로 닫히지 않으므로 닫기 버튼이나
핸들 드래그로 닫습니다.
expandable을 켜면 핸들을 위로 끌어 half ↔ full 디텐트를 토글합니다 — half
상태에서 아래로 끌면 닫힙니다. 높이 전환은 디텐트 단위로 스냅하며, height는 디텐트
초기값으로만 쓰입니다.
상태#
제어 컴포넌트입니다 — 열림/닫힘은 소비자의 open prop이 결정하고, 닫힘
요청(Escape·배경·닫기 버튼·핸들 드래그)은 전부 onClose 콜백으로 전달됩니다.
핸들을 80px 이상 끌어내리면 닫힘 요청이 발생하고, 그 미만은 스냅백합니다.
열림 동안 배경 body 스크롤이 잠깁니다.
Props#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
open | boolean | — | 표시 여부 — 제어 prop |
onClose | () => void | — | Escape·배경·닫기 버튼·핸들 드래그 다운의 닫힘 요청 콜백 |
title | ReactNode | — | 제목 — aria-labelledby 대상이라 필수 |
footer | ReactNode | — | 하단 액션 영역 — 자식이 균등 폭으로 늘어남 |
height | 'auto' | 'half' | 'full' | 'auto' | auto: 내용 높이(상한 85dvh) · half: 50dvh · full: 85dvh. expandable이면 디텐트 초기값 |
variant | 'standard' | 'modal' | 'modal' | modal: 스크림+포커스 트랩+스크롤 잠금 · standard: 본문과 공존(스크림·트랩 없음) |
expandable | boolean | false | 핸들을 위로 끌어 half↔full 디텐트 토글 (half에서 아래로 끌면 닫힘) |
closeLabel | string | '닫기' | 닫기 버튼 aria-label |
접근성#
| 계약 | 구현 |
|---|---|
| 역할 | role="dialog" + aria-modal="true" |
| 이름 | title이 aria-labelledby로 연결 |
| 포커스 진입 | 열리면 패널로 이동 |
| 포커스 트랩 | Tab/Shift+Tab이 시트 안에서 순환 |
| 포커스 복원 | 닫히면 이전 포커스 요소로 복귀 |
| 키보드 닫기 | Escape — 포커스 위치 무관(document 캡처) |
| 드래그 핸들 | 포인터 전용 보조 경로(aria-hidden) — 키보드는 Escape·닫기 버튼 |
| 스크롤 | 열림 동안 배경 body 스크롤 잠금 |
| 모션 | 슬라이드 업은 transform/opacity만 + prefers-reduced-motion 존중 |
토큰#
component 토큰 없이 semantic을 직접 소비합니다(신설 기준 §4 미충족 — Modal과 같은 오버레이 어휘).
| 속성 | 토큰 |
|---|---|
| 오버레이 | color.overlay · z.modal |
| 패널 | color.surface-raised · radius.xl(상단 모서리) · shadow.xl |
| 핸들 | color.border-strong · radius.full |
| 푸터 구분선 | color.border |
| 닫기 버튼 | hover color.surface-hover · pressed color.surface-pressed |
| 등장 모션 | duration.fast/normal · 패널 ease.emphasized-decelerate · 스크림 ease.out |