본문으로 건너뛰기

오버레이

화면 위에 뜨는 것: 툴팁(80) · 팝오버(60) · 대화상자(가림막 40 · 카드 50) · 드로어(Sheet). 층은 토큰으로만 정합니다.

작고 잠깐 뜨는 팝오버는 지붕 모서리가 없는 뜬 판지, 대화상자 · 드로어는 오른쪽 위가 꺾인 떠 있는 카드입니다. 가림막은 blur 없는 단색입니다. 겹친 대화상자는 가림막이 두 겹이라 아래 대화상자까지 덮습니다. 툴팁 · 팝오버에는 화살표가 없습니다. 모두 radix-ui 가 필요합니다.

import
import { Tooltip, TooltipProvider, Popover, Dialog, AlertDialog, Sheet, … } from "@uyoolabs/ui/overlay";
고르는 법
이름표 · 짧은 이유 → Tooltip. 화면을 떠나지 않는 작은 작업(필터 · 공유 · 링크 편집) → Popover. 멈추고 답해야 하는 폼 · 확인 → Dialog. 되돌릴 수 없는 동작 바로 앞 → AlertDialog. 한 항목을 들여다보기 · 조건이 많은 필터 → Sheet.
쓰지 않는 경우
툴팁 안에 버튼 · 링크 · 입력을 두지 않습니다. 꼭 읽어야 하는 정보를 툴팁에만 두지 않습니다. 팝오버 안에서 팝오버 · 대화상자를 열지 않습니다. 대화상자 안에서 또 다른 폼 대화상자를 열지 않습니다(확인용 AlertDialog 한 겹만). 시트 위에 시트를 열지 않습니다. '확인 · 예 · 아니요' 버튼을 쓰지 않습니다.

Tooltip

아이콘 버튼의 이름표 · 단축키 · 짧은 이유를 짙은 반전 면에 담습니다. 포커스를 받지 않습니다.

키동작
포인터를 올림400ms 뒤 열린다(delay)
이웃 트리거로 옮김닫힌 지 300ms 안이면 바로(skipDelay)
키보드 포커스바로 열린다
Esc닫힌다. 포커스는 트리거에 남는다
누름 · 떠남바로 닫힌다. 툴팁 위로 옮겨도 열린 채(WCAG 1.4.13)
터치열지 않는다

Popover

누르면 열리는 비모달 판지입니다. 포커스를 가두지 않고, Tab 이 밖으로 나가면 닫힙니다.

키동작
Enter·Space트리거에서 열고 첫 입력(initialFocus)으로
Tab·Shift+Tab팝오버 안을 돈다. 끝에서 밖으로 나가면 닫힌다
Esc닫고 트리거로 포커스를 돌린다
바깥 누름닫는다. 포커스는 누른 곳에

Dialog

sm 400 · md 560 · lg 720. 본문만 스크롤하고, 넘칠 때만 머리 · 바닥에 헤어라인이 생깁니다. 제출 중에는 dismissible={false} 로 닫힘을 막습니다.

AlertDialog

되돌릴 수 없는 동작의 확인입니다. 제목은 질문, 설명은 결과를 숫자로, 버튼은 하는 일의 동사로 씁니다. 바깥 누름 · 닫기 버튼이 없습니다.

키동작
Tab·Shift+Tab대화상자 안에서만 돈다
Esc닫는다. dismissible=false 면 무시, [data-escape-local] 안이면 양보, AlertDialog 는 취소와 같다
Enter폼 제출. 조합 중 Enter(한 · 일 IME)는 막는다
열 때 · 닫을 때열면 initialFocus → 첫 입력 → 바닥 첫 버튼(AlertDialog 는 [취소]). 닫으면 Trigger → returnFocusRef → 열 때의 요소

Sheet

화면 옆에 꺼내 놓는 떠 있는 카드입니다. 오른쪽은 가장자리에서 8 띄우고, 640 미만에서는 아래에서 올라옵니다(side=auto).

키동작
Tab·Shift+Tab시트 안에서만 돈다
Esc닫는다(dismissible=false 면 무시)
Alt+←onBack 이 있으면 [뒤로]와 같다
Enter폼 제출. 조합 중 Enter 는 막는다

props

소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.

Tooltip

prop타입기본값설명
label필수ReactNode—툴팁 글자. 글자와 인라인 강조만 — 버튼 · 링크 · 입력을 넣지 않는다
keysreadonly string[]—단축키. 키마다 테두리만 있는 키캡(반전 면용)
kind"name" | "description""description""name": 트리거의 이름표(아이콘 버튼). 트리거의 aria-label 과 같은 글자라 aria-describedby 를 걸지 않는다. "description": 덧붙이는 설명(비활성 이유 · 짧은 도움말). 열려 있는 동안 트리거의 aria-describedby 가 된다
side"top" | "right" | "bottom" | "left""top"화면 끝이면 반대로 뒤집힌다
align"start" | "center" | "end""center"
sideOffsetnumber6트리거와의 간격(px)
delaynumber—열기 지연(ms). Provider 값을 덮는다
disabledbooleanfalse툴팁만 끈다(트리거는 그대로)
openboolean—
defaultOpenboolean—
onOpenChange(open: boolean) => void—
children필수ReactElement—트리거. 포커스를 받는 요소 하나. disabled 버튼은 aria-disabled 로 바꿔 넣어 포커스가 남게 한다

TooltipProvider

prop타입기본값설명
delaynumber400포인터를 올린 뒤 열리기까지(ms). 지나가는 포인터에 깜박이지 않게 400
skipDelaynumber300닫힌 지 이 시간 안에 이웃 트리거로 가면 지연 없이 바로 연다(ms)
children필수ReactNode—

PopoverContent

prop타입기본값설명
size"sm" | "md" | "lg""md"240 · 320 · 400
titleReactNode—보이는 제목. aria-labelledby 가 된다. 없으면 aria-label 을 꼭 준다
sideOffsetnumber4트리거와의 간격(px)
align"start" | "center" | "end""start"트리거 기준 맞춤. 기본은 시작선
initialFocus"first" | "content" | RefObject<HTMLElement | null>"first"열 때 포커스. "first" 는 첫 포커스 가능한 요소, "content" 는 판지 자체(읽기 위주)

이 밖에 Radix Popover.Content props(title 제외)를 그대로 받습니다. 기본값을 바꾼 것은 collisionPadding=8 입니다.

Dialog

prop타입기본값설명
openboolean—
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void—
dismissKeystring | number | null—열린 동안 이 값이 바뀌면 onOpenChange(false). 열 때의 값이 기준이다. 앱은 주소 키(useLocationKey)를 넘긴다
returnFocusRefRefObject<HTMLElement | null>—닫은 뒤 포커스를 돌려줄 요소. Trigger 가 없을 때 쓴다. 없으면 열 때의 activeElement
dismissiblebooleantruefalse 면 Esc · 바깥 누름 · 닫기 버튼으로 닫히지 않는다(제출 중 · 입력이 바뀐 폼)
children필수ReactNode—

DialogContent

prop타입기본값설명
size"sm" | "md" | "lg""md"400 · 560 · 720
initialFocusRefObject<HTMLElement | null>—열 때 포커스. 없으면 첫 입력, 그것도 없으면 바닥의 첫 버튼
showClosebooleantrue머리 오른쪽 닫기 버튼(DialogHeader 가 그린다)
lang"ko" | "en" | "ja"—내장 문구(닫기)의 언어
labelsPartial<DialogLabels>—내장 문구 중 바꿀 것

이 밖에 Radix Dialog.Content props(title 제외)를 그대로 받습니다.

DialogTitle

prop타입기본값설명
hiddenboolean—화면에서 숨기고 스크린 리더에만 읽힌다

이 밖에 Radix Dialog.Title props를 그대로 받습니다.

AlertDialog

prop타입기본값설명
openboolean—
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void—
dismissKeystring | number | null—열린 동안 이 값이 바뀌면 onOpenChange(false). Dialog 와 같다
returnFocusRefRefObject<HTMLElement | null>—닫은 뒤 포커스를 돌려줄 요소. Trigger 가 없을 때 쓴다
children필수ReactNode—

AlertDialogContent

prop타입기본값설명
size"sm" | "md""sm"400 · 560
lang"ko" | "en" | "ja"—내장 문구(취소 · 이름 입력 라벨)의 언어
labelsPartial<AlertDialogLabels>—내장 문구 중 바꿀 것

이 밖에 Radix AlertDialog.Content props를 그대로 받습니다.

AlertDialogAction

prop타입기본값설명
variant"danger" | "primary""danger""primary" 는 되돌릴 수 있는데 확인이 필요한 드문 경우만
loadingbooleanfalse스피너 · aria-busy. 그동안 [취소] · Esc 가 막힌다

이 밖에 Radix AlertDialog.Action props를 그대로 받습니다.

AlertDialogConfirmInput

prop타입기본값설명
expected필수string—입력해야 하는 글자(프로젝트 이름). 앞뒤 공백을 지우고 NFC 로 맞춘 뒤 정확히 같아야 한다(대소문자 구분)
valuestring—
defaultValuestring""
onValueChange(value: string) => void—
onMatchChange(matched: boolean) => void—
labelReactNode—라벨. 없으면 labels.confirmPrompt(expected) 이고 이름을 굵게 보인다

이 밖에 <input> props(value · defaultValue · size 제외)를 그대로 받습니다.

Sheet

prop타입기본값설명
openboolean—
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void—
dismissKeystring | number | null—열린 동안 이 값이 바뀌면 onOpenChange(false). 상세 보기는 주소 키를 넘긴다(뒤로 가기가 닫는다)
returnFocusRefRefObject<HTMLElement | null>—닫은 뒤 포커스를 돌려줄 요소. 주소로 열려 Trigger 가 없을 때 쓴다. 없으면 열 때의 activeElement
dismissiblebooleantruefalse 면 Esc · 바깥 누름 · 닫기 버튼으로 닫히지 않는다(저장 중)
children필수ReactNode—

SheetContent

prop타입기본값설명
side"right" | "bottom" | "auto""auto""auto": 640 미만 bottom, 이상 right. data-side 에는 실제 쪽이 적힌다
size"sm" | "md" | "lg""md"360 · 480 · 640. 화면이 시트 + 320 보다 좁으면 한 단계 작게
initialFocus"title" | "first" | RefObject<HTMLElement | null>"first"열 때 포커스. "first" 는 첫 입력, 상세 보기는 "title"(읽으러 연 사람이 입력칸부터 만나지 않게)
onBack() => void—있으면 머리 왼쪽에 [뒤로]. Alt+← 도 같다. 시트 안에서 다른 항목으로 옮겼을 때
showClosebooleantrue머리 오른쪽 닫기 버튼
lang"ko" | "en" | "ja"—내장 문구(닫기 · 뒤로)의 언어
labelsPartial<SheetLabels>—내장 문구 중 바꿀 것

이 밖에 Radix Dialog.Content props(title 제외)를 그대로 받습니다.