BottomSheet
모바일 선택과 확인 흐름을 native modal dialog, focus 관리, scroll lock으로 표준화합니다.
예제
BottomSheet modal lifecycle와 긴 본문
세 종료 사유, dismissible 정책, 초기·복원 focus, 내부 scroll과 고정 footer를 확인하세요.
닫은 뒤 focus가 이 열기 버튼으로 복원되는지 확인하세요.
사용해야 할 때
모바일 화면에서 현재 문맥을 유지한 채 배송지 선택, 약관 확인, 짧은 의사결정 같은 집중 작업을 완료할 때 사용합니다. 배경 콘텐츠와 상호작용을 잠시 차단하고 명확한 제목, 본문, 선택 footer를 함께 제공해야 하는 흐름에 적합합니다.
사용하지 말아야 할 때
긴 다단계 작업, URL로 공유해야 하는 정보, 전체 화면 탐색에는 별도 page나 route를 사용합니다. drag-to-dismiss, snap point, 중첩 sheet, device keyboard 높이 보정이 필요하면 이 v0.3 계약을 임의로 확장하지 않고 별도 패턴을 정의합니다.
구조
component는 hydration 뒤 document.body 또는 portalContainer로 native dialog를 Portal합니다. dialog 안에는 surface, title과 선택 description을 가진 header, 항상 존재하는 owned Close IconButton, 독립 scroll body, 선택 footer가 있습니다. stable ID가 aria-labelledby와 aria-describedby를 연결합니다.
크기와 변형
mobile에서는 full width이고 넓은 화면에서는 최대 640px입니다. 높이는 content에 맞되 calc(100dvh - 24px)를 넘지 않아 full specimen에서도 viewport 안에 남습니다. Figma의 Height=Content|Full과 Footer=Hidden|Visible 축은 네 open-state specimen을 만들고 React에서는 실제 content와 footer 유무가 layout을 결정합니다.
상태와 동작
open은 항상 controlled입니다. Close button은 close-button, Escape는 escape, 같은 pointer가 dialog 배경에서 시작하고 끝나면 backdrop reason을 전달합니다. dismissible=false는 Escape와 backdrop만 막고 close button은 남깁니다. 닫을 때 medium exit가 끝날 때까지 mounted 상태를 유지한 뒤 native close()를 호출합니다.
반응형 동작
surface는 mobile, tablet, desktop에서 viewport 하단에 정렬됩니다. header와 footer는 줄어들지 않고 body만 내부 scroll을 소유합니다. 긴 unbroken copy는 body 너비 안에서 줄바꿈되며 footer action은 본문 scroll과 관계없이 계속 보입니다.
접근성
showModal()이 background inertness와 native focus containment를 제공합니다. 열기 전 active element를 보관하고 유효한 initialFocusRef 또는 close button으로 focus를 이동하며, exit 뒤 기존 element가 연결되어 있으면 focus를 복원합니다. body scroll lock은 여러 open instance를 참조계수로 관리하고 마지막 instance가 닫힐 때 정확한 이전 inline overflow를 복원합니다. reduced motion에서는 exit를 즉시 완료하고 transform을 제거합니다.
React 예제
import { useRef, useState } from 'react';
import {
BottomSheet,
Button,
type BottomSheetCloseReason,
} from '@hds/react';
import '@hds/react/styles.css';
export function AddressSheet() {
const [open, setOpen] = useState(false);
const initialFocusRef = useRef<HTMLButtonElement>(null);
function handleOpenChange(
nextOpen: boolean,
reason: BottomSheetCloseReason,
) {
console.info(reason);
setOpen(nextOpen);
}
return (
<>
<Button onClick={() => setOpen(true)}>배송지 열기</Button>
<BottomSheet
closeLabel="배송지 닫기"
initialFocusRef={initialFocusRef}
open={open}
title="배송지 선택"
onOpenChange={handleOpenChange}
>
<Button ref={initialFocusRef}>배송지 선택하기</Button>
</BottomSheet>
</>
);
}
API
| Prop | Type | Default | 설명 |
|---|---|---|---|
open |
boolean |
required | controlled open state |
onOpenChange |
(open: boolean, reason: BottomSheetCloseReason) => void |
required | 세 종료 사유를 가진 controlled request |
title |
string |
required | localizable visible dialog heading |
description |
string |
- | 선택적인 accessible description |
children |
ReactNode |
required | 내부 scroll body |
footer |
ReactNode |
- | body와 분리된 footer |
closeLabel |
string |
required | owned close button accessible name |
dismissible |
boolean |
true |
Escape와 backdrop 허용 여부 |
portalContainer |
HTMLElement | null |
document.body after hydration |
Portal container |
initialFocusRef |
RefObject<HTMLElement | null> |
owned close button | dialog 내부 초기 focus 대상 |
사용 토큰
size/control/small,size/icon/medium,space/0,space/2,space/4,space/8,space/12,space/16,space/24,space/64radius/xl,font/family/sans,font/size/body-sm,font/size/body,font/size/title-sm,font/weight/semibold,font/line-height/body-sm,font/line-height/body,font/line-height/title-sm,elevation/2motion/duration/fast,motion/duration/medium,motion/easing/standard,color/bg/scrim,color/bg/surface,color/bg/subtle,color/text/*,color/border/*,color/action/*,color/focus/ring
Figma
BottomSheet component set은 Height Content/Full × Footer Hidden/Visible = 4 open-state variants와 Title, Description TEXT, Show description BOOLEAN property를 제공합니다. 모든 variant가 owned Close instance를 사용하고 Footer Visible variant는 owned Large Fill Button instance를 재사용합니다. closed state는 invisible variant가 아니라 behavior로 문서화합니다. 구현 방향은 TDS Mobile BottomSheet reference와 함께 검토합니다.
지원 상태
| React | Svelte | React Native |
|---|---|---|
| preview | planned | planned |