Date Picker
날짜 · 기간 · 여러 날을 고르는 달력. 날짜 칸(Input Button)을 누르면 1280 미만은 아래 시트 · 이상은 칸 아래 팝오버로 열리고, "완료" 로 넣는다 — 날짜 고르기가 그 화면의 주된 일이면 화면에 바로 펼친다(지금 쓰는 곳은 없다 — "어디에 놓나"). 날짜를 치는 칸은 두지 않는다. 시각은 Time Picker 를 날짜 칸 옆에 나란히, 달만 고를 때는 Wheel Picker 다.
구조는 당근 SEED Date Picker(Apache-2.0)를 따른다 — 머리(연 · 월 제목 · 이전 · 다음) · 요일 줄 · 날짜 칸(원 · 숫자) · 기간 띠, 하루 · 기간 · 여러 날, 한 달 · 두 달 · 이어지는 달. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-10-03 사용자 결정). 옛 Calendar 스펙을 대신한다.
수치 원본은 date-picker.yaml이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.
직접 골라 보기
고르기(하루 · 기간 · 여러 날) · 여는 자리(시트 · 팝오버) · 막힌 날 · 빠른 기간을 고르면 스펙대로 그린 달력과 그 코드가 바뀐다. 실제로 날짜를 누르고 "완료" 로 칸에 넣을 수 있다.
날짜를 누르고 "완료" 를 누르면 칸에 들어가요. 바깥 · 닫기 · Esc 는 고르던 것을 버려요.
import { DatePicker } from "@/components/ui/date-picker"
// 1280 미만 — BottomSheetContent 의 본문 — useInputButtonSurface(input-button.md)
<DatePicker
selection="single"
autoFocus
value={draft}
onValueChange={setDraft}
/>
<Button disabled={!draft} onClick={apply}>완료</Button>Anatomy
| 부위 | 설명 |
|---|---|
| ⓐ Presets | 빠른 기간 — 기간 달력 위 칩 한 줄. 누르면 달력에 칠한다. |
| ⓑ Header | 머리 — 왼쪽 연 · 월 제목, 오른쪽 이전 · 다음. |
| ⓒ Title | 연 · 월 제목 — 누르면 날짜 자리에 연 · 월 휠이 뜬다. |
| ⓓ Nav Button | 이전 · 다음 — 한 달씩. |
| ⓔ Weekday | 요일 줄 — "일" … "토". |
| ⓕ Cell | 날짜 칸 — 칸 전체가 누르는 자리. |
| ⓖ Day | 날짜 원 · 숫자 — 오늘 · 고름 · 막힘을 그린다. |
| ⓗ Range Band | 기간 띠 — 시작 · 끝 원 사이. |
| ⓘ Footer | 바닥 — "완료"(기간은 "초기화" + "완료"). 시트 · 팝오버의 바닥이다. |
부위
| 부위 | 설명 |
|---|---|
root | 달력 — 시트 · 팝오버의 본문 |
presets | 빠른 기간 — 기간 달력 위 칩 한 줄(하나 고르기) |
header | 머리 — 왼쪽 연 · 월 제목, 오른쪽 이전 · 다음 |
title | 연 · 월 제목 — 누르면 날짜 자리에 연 · 월 휠 |
titleIcon | 제목 셰브론 — 휠이 열리면 위로 |
navButton | 이전 · 다음 — 한 달씩(두 달 보기는 한 달씩, PageUp · PageDown 은 두 달) |
weekday | 요일 줄 — "일" … "토", 읽는 이름은 "일요일" … |
cell | 날짜 칸 — 칸 전체가 누르는 자리 |
dayVisual | 날짜 원 — 오늘 · 고름 · 호버 |
dayLabel | 날짜 숫자 |
rangeBand | 기간 띠 — 시작 · 끝 원 사이를 잇는다 |
monthLabel | 달 이름 — 이어지는 달 · 두 달 보기에서 달마다 |
wheel | 연 · 월 휠 — 요일 줄과 날짜 자리를 덮는다(Wheel Picker) |
fog | 아래 안개 — 이어지는 달의 아래 끝 |
footer | 바닥 — "완료"(기간은 "초기화" + "완료") |
focusRing | 키보드 포커스 링(웹) |
Properties
머리 · 요일 · 날짜 칸
머리 · 요일 줄 · 날짜 줄은 모두 48 이다. 날짜 칸은 달력 폭을 7로 나눈 폭에 높이 48 이고 칸 전체가 누르는 자리다 — 원 42 는 칸 위 3 에 놓여 원 · 띠끼리 6 떨어진다. 숫자는 16 / 22 · 500, 요일은 14 / 19 · 500 fg-neutral-subtle. 제목은 16 / 22 · 700 에 셰브론 20, 이전 · 다음은 Button ghost 40(아이콘 18)을 오른쪽 끝에 붙인다.
달력은 늘 6주다 — 앞뒤 달 날짜를 흐리게 채워 달을 넘겨도 머리 · 바닥이 움직이지 않는다. 앞뒤 달 날짜는 보이기만 하고 누르지 못한다(보조 기술에도 숨긴다) — 다른 달은 이전 · 다음 · 화살표 키로 넘긴다.
칸 전체가 누르는 자리 — 원 42 은 칸 위 3 · 가운데라 원 · 띠끼리 6 떨어진다. 숫자 16 / 22 · 500, 요일 14 / 19 · 500. 9월 27일~30일 · 11월 1일~7일(앞뒤 달)은 흐리게 채우기만 하고 누르지 못한다 — 늘 6주라 달을 넘겨도 높이가 그대로다.
공통
| 부위 | 속성 | 값 | 비고 |
|---|---|---|---|
root | 너비 | 336px | 팝오버 — 칸 48 × 7. 시트는 시트 폭 − 좌우 24 · 24(360 화면 312 · 칸 44.6) |
root | 배경 | color-bg-layer-floating #FFFFFF · 다크 #2D3346 | |
presets | 칩 | Chip outlineStrong medium 36 | 하나 고르기(라디오) — 고른 칩은 짙은 채움, 달력에서 다른 날을 누르면 풀린다. 이름 · 범위는 date-picker.md 의 '빠른 기간' |
presets | 간격 | spacing-between-chips 8px | 칩 사이 8 — 시트는 한 줄 가로 스크롤(화면 끝까지), 팝오버는 줄바꿈 |
presets | 아래 여백 | spacing-x3 12px | 칩 줄 ↔ 머리 12 |
header | 높이 | 48px | 최소 — 글자가 크면 그만큼만 늘어난다. 칸 · 머리 · 요일 줄 · 원 · 이전 · 다음은 px, 글자만 배율(SEED, 03-C 1B) |
title | 글자 | text-t5 16px / 22px | |
title | 굵기 | 700 | |
title | 글자·아이콘 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
title | 좌우 여백 | spacing-x1 4px | 제목 글자는 머리 왼쪽에서 4 — 누르는 자리는 머리 높이 48 전체 |
title | 간격 | spacing-x1 4px | 글자 ↔ 셰브론 |
title | 모서리 | radius-r2 8px | 바탕이 없어 초점 링에만 보인다 |
titleIcon | 크기 | 20px | lucide chevron-down |
titleIcon | 색 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
navButton | 크기 | 40px | Button ghost iconOnly medium 을 px 로 — 글자 크기를 따르지 않는다(03-C 1B). 둘 사이 0, 머리 오른쪽 끝, 누르는 영역 44 |
navButton | 아이콘 크기 | 18px | lucide chevron-left · chevron-right |
navButton | 색 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
weekday | 높이 | 48px | 최소 — 글자가 크면 그만큼만 늘어난다(03-C 1B) |
weekday | 글자 | text-t4 14px / 19px | |
weekday | 굵기 | 500 | |
weekday | 글자·아이콘 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | |
cell | 높이 | 48px | 최소(SEED dateCell minHeight) — 글자가 크면 그만큼만 늘어난다(03-C 1B): max(48, 숫자 줄 높이 + 위아래 3 · 3) — 날짜 버튼 · 앞뒤 달 숫자가 위아래 3 을 가진다(200% 44 + 6 = 50 · 달력 396). 폭은 달력 폭 ÷ 7 — 칸 전체가 누르는 자리, 칸 사이 0 |
cell | 주 수 | 6 | 늘 6주 — 달을 넘겨도 머리 · 바닥이 움직이지 않는다(이어지는 달만 그 달의 주 수) |
cell | 커서 | pointer | |
dayVisual | 크기 | 42px | px — 글자 크기를 따르지 않는다(03-C 1B, SEED). 칸 위 3 · 가로 가운데 — 원 · 띠끼리 6 떨어진다. 칸이 44 보다 좁으면(320 화면) 칸 폭 − 2 |
dayVisual | 모서리 | radius-full 9999px | |
dayVisual | 배경 | transparent | |
dayVisual | 전환 대상 | background-color | |
dayVisual | 전환 시간 | motion-duration-color-transition 150ms | |
dayVisual | 전환 곡선 | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | |
dayLabel | 글자 | text-t5 16px / 22px | 숫자 폭을 같게(tabular-nums) |
dayLabel | 굵기 | 500 | |
dayLabel | 글자·아이콘 | color-fg-neutral-muted #535866 · 다크 #B7BDCC | |
rangeBand | 높이 | 42px | 칸 위 3 — 원과 같은 높이(칸이 좁아 원이 줄면 띠도 같이). 시작 칸은 원 가운데부터 오른쪽, 끝 칸은 왼쪽부터 원 가운데까지, 사이 칸은 칸 폭 전체 — 줄 끝에서 둥글리지 않고 칸 끝에서 끊는다 |
rangeBand | 배경 | color-bg-neutral-weak #F5F6FA · 다크 #353B4D | |
monthLabel | 높이 | 48px | 최소 — 글자가 크면 그만큼만 늘어난다(03-C 1B) |
monthLabel | 글자 | text-t5 16px / 22px | |
monthLabel | 굵기 | 700 | |
monthLabel | 글자·아이콘 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
wheel | 높이 | 336px | 요일 줄 + 6주 자리를 덮는다 — 높이는 요일 줄 + 6 × 칸이라 글이 크면 달력을 따라 늘어난다(100% 요일 줄 48 + 6주 288 · 200% 48 + 50 × 6 → 348, 열고 닫아도 달력 높이가 그대로). Wheel Picker medium 44 × 7 = 308 을 가운데에(100% 위아래 14). 열린 동안 이전 · 다음은 막힌다 |
wheel | 칼럼 | 연 120 · 월 96 | 연은 오른쪽 · 월은 왼쪽 정렬로 가운데에 모은다(SEED 와 같다) — 항목은 "2026년" · "10월" |
wheel | 배경 | color-bg-layer-floating #FFFFFF · 다크 #2D3346 | |
footer | 위 여백 | spacing-x4 16px | 팝오버 — 달력 아래 16, 버튼 small 36 오른쪽 맞춤. 시트는 Bottom Sheet 바닥(위 12 · 아래 16, large 48 — 하나면 폭 전체, 둘이면 반씩) |
팝오버의 달력은 336(칸 48 × 7), 시트는 시트 폭에서 좌우 24 를 뺀 폭이다(360 화면 312). 칸이 44 보다 좁으면(320 화면) 원을 칸 폭 − 2 로 줄인다.
글자 크기 설정은 글자(숫자 · 요일 · 제목)만 따른다. 칸 · 머리 · 요일 줄은 최소 48 이고 글이 더 크면 그만큼만 늘어나며, 원 42 · 이전 · 다음 40 은 px 그대로다(SEED — dateCell minHeight). 날짜 칸은 숫자 줄 높이에 위아래 3 · 3 을 더한 높이와 48 중 큰 쪽이다 — 날짜 버튼과 앞뒤 달 숫자가 위아래 3 을 가지므로 숫자 줄이 원 42 를 넘는 큰 글자에서 칸이 줄 높이 + 6 이 된다(100% 48 · 200% 44 + 6 = 50). 웹 · 앱이 같은 모양이다. 글자 200% 에서 달력은 396(머리 48 · 요일 48 · 칸 50 × 6) — 시트 안에 6주가 다 들고 제목이 한 줄로 보인다. 이어지는 달의 달 높이(이름 줄 + 칸 × 주 + 16)도 같은 칸 높이로 셈하고, 배율이 바뀌면 다시 재서 보던 달로 맞춘다. 연 · 월 휠 자리도 요일 줄 + 6 × 칸이라 함께 늘어난다(100% 336 · 200% 348) — 제목을 눌러 휠을 열고 닫아도 달력 높이가 그대로다(2026-10-22 I-04 검수 — 레시피가 칸에 위아래 3 을 두지 않아 200% 에서 384 였다). 대가 — 두 자리 숫자(32px)가 원 42 를 채워 이웃 숫자와 약 6 떨어진다(SEED 와 같은 값)(2026-10-21 사용자 결정 — 03-C 1B, 비교 페이지 https://claude.ai/artifact/3nWUZCYpPh2Cg3uXc3DKtp).
Selection
| 값 | 쓰는 곳 |
|---|---|
single | 날짜 하나 — 일정 · 거래 · 할 일 마감 · 저축 기한 · 환불일 |
range | 기간 — 통계 · 가계부 필터 · 내보내기 · 검색 · 업무 보고 · 공지 · 유효기간 |
multiple | 여러 날 — 따로 고른 날들. 지금 쓰는 곳은 없다(SEED 와 같게 둔다) |
고르기
| selection | 설명 |
|---|---|
single (기본) | 하루 — 날짜 · 기한 · 약속일 |
range | 기간 — 시작 · 끝, 같은 날을 두 번 누르면 하루짜리 기간 |
multiple | 여러 날 — 따로 고른 날들, 띠로 잇지 않는다. 다시 누르면 풀린다 |
Visible Range
하루 · 여러 날은 한 달(month)이다. 기간은 여는 자리에 따라 바뀐다 — 1280 미만 시트는 달이 위아래로 이어지고(continuous, 스크롤로 달을 넘긴다 · 요일 줄이 위에 붙는다 · 아래 안개 96), 1280 이상 팝오버는 두 달을 나란히(twoMonths, 사이 24 — 팝오버 폭 744 는 Popover 최대 480 의 예외다).
보이는 범위
| visibleRange | 높이 | 너비 | 칸 간격 | header 높이 | title 글자 | weekday 자리 | cell 주 수 | monthLabel 좌우 여백 | fog 높이 | 설명 |
|---|---|---|---|---|---|---|---|---|---|---|
month (기본) | — | — | — | — | — | — | — | — | — | 한 달 — 하루 · 여러 날, 그리고 시트 · 팝오버가 아닌 자리 |
twoMonths | — | 696px — 336 + 24 + 336 — 팝오버 폭 744(Popover 최대 480 의 예외) | spacing-x6 24px — 두 달 사이 24 | 48px — 머리를 나누지 않는다 — 달 이름은 달마다 가운데, 이전 버튼은 첫 달 왼쪽 끝 · 다음 버튼은 둘째 달 오른쪽 끝(위 4) | text-t5 16px / 22px — 달 이름은 누르지 않는다 — 연 · 월 휠이 없다 | — | — | — | — | 두 달 나란히 — 1280 이상 팝오버의 기간 |
continuous | 시트에 맞춘다 — 시트를 화면 높이의 90% 로 열고 머리 · 칩 줄 · 바닥을 뺀 높이 — 그 안에서 달이 위아래로 이어진다(스크롤바 숨김) | — | — | 0px — 머리 없음 — 스크롤이 달을 넘긴다. 연 · 월 휠도 없다 | — | sticky — 맨 위에 붙는다 — 달마다의 요일 줄은 없다 | 그 달의 주 수 — 4 ~ 6 — 앞뒤 달 날짜는 비운다(달이 이어져 다음 달이 바로 아래에 있다) | spacing-x1 4px — 왼쪽 정렬, 달 아래 16 | 96px — 아래 끝 — 투명에서 바탕색으로 | 달이 위아래로 이어진다 — 1280 미만 시트의 기간 |
State
| 상태 | 모습 |
|---|---|
enabled | 숫자 fg-neutral-muted · 원 없음 |
hovered | 웹 — 마우스를 올린 날짜에 옅은 원(bg-neutral-weak-alpha-pressed — SEED 투명 색, 바탕 1.15 · 다크 1.77). 오늘 · 기간 사이 날 위에서는 불투명 bg-neutral-weak-pressed |
pressed | 터치 — 누르는 동안 같은 원. 날짜는 줄지 않는다 |
focused | 키보드 초점이 온 날짜의 원 둘레 안쪽 2px 링 — 웹 · 앱(앱은 하드웨어 키보드 · 스위치 접근일 때만) |
today | 옅은 원(bg-neutral-weak) + 숫자 700 · fg-neutral |
selected | 짙은 원(bg-neutral-inverted) + 숫자 fg-neutral-inverted — 하루 · 여러 날의 고른 날, 기간의 시작 · 끝 |
inRange | 기간의 사이 날 — 원 없이 띠(bg-neutral-weak) |
outside | 앞뒤 달 — 흐린 숫자(fg-disabled), 누르지 못함 |
disabled | 고를 수 없는 날 — 흐린 숫자 + 취소선. 초점은 간다(aria-disabled) |
readOnly | 확정된 시작일(기간 늘리기) — 흐린 채움(stroke-neutral-solid) + 숫자 fg-neutral-inverted, 누르지 못함. 그 앞의 날은 끝이 될 수 없어 막힌 날로 그린다 |
상태
| 상태 | cell 커서 | dayVisual 배경 | 취소선 | dayLabel 글자·아이콘 | dayLabel 굵기 | 링 두께 | 링 간격 | 링 색 |
|---|---|---|---|---|---|---|---|---|
enabled 이번 달 날짜 | pointer | transparent | — | color-fg-neutral-muted #535866 · 다크 #B7BDCC | 500 | — | — | — |
hovered 웹 — 마우스를 올린 날짜의 원(고운 포인터만) | pointer | color-bg-neutral-weak-alpha-pressed #00000010 · 다크 #FFFFFF2E — SEED 투명 색 — 바탕 1.15 · 다크 1.77(2026-10-21 03-C 2B). 마우스(고운 포인터)만 — 축소 없음. 오늘(옅은 원) · 기간 사이 날(띠) 위에서는 불투명 bg-neutral-weak-pressed(투명 색을 띠에 얹으면 또 다른 색이 된다). 막힌 날 · 읽기 전용 · 앞뒤 달에는 없다 | — | color-fg-neutral-muted #535866 · 다크 #B7BDCC | 500 | — | — | — |
pressed 누르는 동안 원(터치) — 날짜는 줄지 않는다 | pointer | color-bg-neutral-weak-alpha-pressed #00000010 · 다크 #FFFFFF2E — 터치는 누르는 동안 — 오늘 · 기간 사이 날 위에서는 불투명 bg-neutral-weak-pressed. 날짜는 줄지 않는다(이전 · 다음은 Button 처럼 2px 축소) | — | color-fg-neutral-muted #535866 · 다크 #B7BDCC | 500 | — | — | — |
focused 키보드 초점이 온 날짜에 링(웹 · 앱 — 앱은 하드웨어 키보드 · 스위치 접근, 03-C 4A) | pointer | transparent | — | color-fg-neutral-muted #535866 · 다크 #B7BDCC | 500 | 2px | -2px — 원 자리(42) 안쪽 — 칸이 아니라 원 둘레 | color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA |
today 오늘 — 옅은 원 + 숫자 굵게 | pointer | color-bg-neutral-weak #F5F6FA · 다크 #353B4D | — | color-fg-neutral #1A1F2E · 다크 #F5F6FA | 700 — 색이 아니라 굵기로도 알린다 — 고른 날 · 기간 안에서도 굵기는 남는다 | — | — | — |
selected 고른 날 · 기간의 시작 · 끝 — 짙은 원 | pointer | color-bg-neutral-inverted #1A1F2E · 다크 #F5F6FA — 하루 · 여러 날의 고른 날, 기간의 시작 · 끝 — 오늘이어도 이 모양이 이긴다(굵기는 남는다) | — | color-fg-neutral-inverted #FFFFFF · 다크 #242938 | 500 | — | — | — |
inRange 기간의 사이 날 — 띠 | pointer | transparent — 띠가 바탕 — 오늘이어도 원은 그리지 않는다(굵기만 남는다) | — | color-fg-neutral-muted #535866 · 다크 #B7BDCC | 500 | — | — | — |
outside 앞뒤 달 날짜 — 흐리게 보이기만, 누르지 못한다 | default | transparent | — | color-fg-disabled #8A91A0 · 다크 #838997 — 앞뒤 달 날짜 — 보이기만 하고 누르지 못한다(숫자만 보조 기술에 숨기고 칸은 남겨 요일 짝을 지킨다). 기간 띠도 그리지 않는다. 다른 달은 이전 · 다음 · 화살표 키로 | 500 | — | — | — |
disabled 고를 수 없는 날 — 흐린 글자 + 취소선, 초점은 간다 | not-allowed | transparent | line-through — 고를 수 없는 날 — 흐린 색과 함께 취소선. aria-disabled 라 키보드 초점은 간다 | color-fg-disabled #8A91A0 · 다크 #838997 | 500 | — | — | — |
readOnly 확정된 시작일(기간 늘리기) — 흐린 채움, 누르지 못한다 | default | color-stroke-neutral-solid #767C8B · 다크 #838997 — 확정된 시작일 — 채움은 남기고 톤을 낮춘다(SEED palette gray-600 의 역할 짝). 누르지 못하지만 초점은 간다 | — | color-fg-neutral-inverted #FFFFFF · 다크 #242938 | 500 | — | — | — |
상태가 겹칠 때
- 고름이 이긴다 — 오늘을 고르면 짙은 원이다. 오늘의 굵기(700)는 남는다.
- 기간 안의 오늘은 원 없이 띠 위에 굵은 숫자다.
- 막힌 날이 기간 안에 있으면 띠는 그대로 깔리고 숫자만 흐린 취소선이다. 미완성 기간(시작만 고름)의 시작일이 막힌 날이어도 시작 원 위에는 취소선을 긋지 않는다.
- 오늘인데 막힌 날은 원 없이 굵은 숫자 + 취소선, 고른 날인데 막힌 날은 고른 모양 + 취소선이다.
- 막힌 날 · 읽기 전용 · 앞뒤 달에는 호버 원이 없다.
모션
| 전환 | 시간 | 곡선 | 대상 | 비고 |
|---|---|---|---|---|
날짜 바탕 | motion-duration-color-transition 150ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | background-color | |
제목 셰브론 | motion-duration-d3 150ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | rotate 0 → 180° | 연 · 월 휠이 열리면 — 휠과 날짜는 모션 없이 바뀐다 |
누름 — 이전 · 다음 | motion-duration-pressed-scale 150ms | motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1) | scale | Button 과 같은 2px 거리 축소 — 날짜 칸은 줄지 않는다 |
Guidelines
날짜는 고르게 한다
날짜를 치는 칸은 두지 않는다 — 날짜 칸은 Input Button 이고, 누르면 이 달력이 열린다. 고르는 동안 칸 값은 그대로이고 "완료" 를 누를 때 들어간다. 바깥 · 끌어내리기 · Esc 로 닫으면 고르던 것은 버린다. 열 때는 칸의 값에서 시작한다 — 비었으면 오늘이 든 달을 열고 오늘을 짚기만 한다(고른 것처럼 칠하지 않는다). "완료" 는 하루를 고르기 전, 기간의 끝을 고르기 전에는 막힌다.
어디에 놓나 — 시트 · 팝오버 · 펼친 달력
SEED 는 "날짜 선택이 화면에서 차지하는 비중과 플랫폼에 따라" 놓는 자리(Layout)를 고른다 — 폼의 칸이면 시트 · 팝오버, 날짜 고르기가 그 화면의 핵심 작업이거나 여러 단계 흐름의 한 단계면 화면에 바로 펼친 달력(Inline)이다. 시트 · 팝오버의 1280 은 사용자 결정이고, 펼친 달력은 SEED 와 같게 두되 지금 쓰는 곳은 없다(multiple · 읽기 전용과 같은 결 — 2026-10-22 사용자 결정 1C · 2B · 3B, https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L).
| 상황 | 고를 것 | 확정 |
|---|---|---|
| 폼의 날짜 칸 — 폰 · 1280 미만(거래 · 일정 · 할 일 마감 · 환불일) | Input Button → 아래 시트 + 달력(한 달) | 시트 바닥 "완료" |
| 같은 칸 — 1280 이상 웹 | Input Button → 칸 아래 팝오버 + 달력(336) | 팝오버 바닥 "완료" |
| 기간(통계 · 내보내기 · 검색) | 칸 하나 → 시트는 이어지는 달 · 팝오버는 두 달 + 빠른 기간 칩 | "초기화" + "완료" |
| 날짜 고르기가 그 화면의 주된 일 · 여러 단계 흐름의 한 단계 | 펼친 달력(Inline) — 화면에 바로 둔다. 지금 쓰는 곳은 없다 | 화면의 "다음" · "완료" |
| 시각 칸 | Input Button → Time Picker(시트 · 팝오버). 날짜와 함께면 날짜 칸 옆에 시각 칸(사용자 결정) | "완료" |
| 날짜가 주된 단계이고 시각은 덧붙는 값 | 달력은 펼치고, 시각은 그 아래 칸 → 시트(SEED — Time Picker 의 "날짜와 함께") | 화면의 "다음" |
| 달만(예산 · 홈 · 카드 실적) | Input Button → 연 | 월 휠(Wheel Picker) | "완료" |
| 먼 날(몇 해 앞뒤 — 적금 만기 · 입사일) | 같은 달력 — 제목을 눌러 연 · 월 휠로 옮긴 뒤 날을 누른다(아래 "연 · 월 옮기기") | "완료" |
| 정해진 목록이 아닌 값(금액 · 메모) | Input — 치는 칸. 날짜 · 시각은 치지 않는다(사용자 결정) | — |
| 펼친 달력(Inline) | 시트 | 팝오버 | 휠(달만 · 시각) | 치는 칸 | |
|---|---|---|---|---|---|
| 보이는 자리 | 화면 흐름 안에 늘 | 아래에서 올라온다 · 딤 | 칸 아래 8 · 딤 없음 | 시트 · 팝오버 안 | — |
| 여는 법 | 열 것이 없다 | Input Button | Input Button | Input Button | — |
| 확정 | 화면의 "다음" · "완료" | "완료" | "완료" | "완료" | — |
| 폭 | 화면 폭 − 좌우 24 | 시트 폭 − 48(360 → 312) | 336(두 달 696) | 휠 220 높이 · 5칸 | — |
| 쓰는 때 | 날짜가 그 화면의 주인공 | 1280 미만 폼 | 1280 이상 폼 | 순서 있는 값 · 날은 아니다 | groo 는 두지 않는다 |
| groo 지금 | 쓰는 곳 없음(SEED 와 같게 둔다) | 결정 | 결정 | 결정 | 결정(두지 않는다) |
예외 · 주의
- 펼친 달력은 확정 버튼이 없다 — 화면의 "다음" 이 확정이고, 고른 날은 달력 아래 한 줄로 되짚는다("2027년 3월 12일 (금)까지 · 141일 남았어요").
- 펼친 달력도 달력 모양 · 키 · 6주 · 막힌 날은 시트 · 팝오버와 같다 — 바뀌는 것은 놓이는 자리와 확정뿐이다.
- 폼 안에 달력을 펼쳐 두지 않는다 — 폼의 여러 칸 가운데 하나면 칸이다. 펼치면 폼이 두 배로 길어지고 다른 칸이 밀린다.
- 먼 날짜가 잦은 칸(입사일 · 만기일)은 열 때 그 자리의 흔한 해로 시작해도 된다 — 연의 범위는 자리마다 정한다(아래 "연 · 월 옮기기").
기간 — 칸 하나, 달력 하나
기간은 시작 · 종료 두 칸으로 나누지 않는다 — 칸 하나("9월 28일~10월 6일")를 누르면 한 달력에서 시작과 끝을 고른다.
- 첫 탭이 시작, 시작 뒤의 날을 누르면 끝 — 사이가 띠로 이어진다.
- 시작보다 앞을 누르면 그날이 새 시작이다(둘을 바꾸지 않는다).
- 같은 날을 두 번 누르면 하루짜리 기간이다. 하루짜리를 막아야 하는 자리는 "2일 이상" 같은 제약을 둔다.
- 기간이 완성된 뒤 다시 누르면 새로 시작한다.
- 끝을 고르는 동안 미리 칠해 보이지 않는다. 시작을 고르는 순간 제약에 걸린 날이 한꺼번에 막힌 날로 바뀔 수 있다 — 그런 제약(최소 · 최대 기간)은 달력 위 Callout 으로 미리 알린다.
같은 날을 두 번 누르면 하루짜리 기간이고, 다 고른 뒤 다시 누르면 새로 시작한다. 끝을 고르는 동안 띠를 미리 칠하지 않고, 끝을 고르기 전에는 "완료" 가 막힌다.
빠른 기간
기간 달력 위에 칩 한 줄을 둔다. 누르면 그 기간을 달력에 칠하고(그 달로 옮긴다) "완료" 로 넣는다 — 하나 고르기라 고른 칩은 짙게 채워지고, 달력에서 다른 날을 누르면 칩 고름이 풀린다. 이름과 범위는 아래 한 벌이고, 화면은 이 안에서 고른다(이름이 같으면 어디서나 같은 기간이다).
| 이름 | 기간(오늘이 2026년 10월 2일이면) |
|---|---|
| 이번 주 | 일요일 ~ 토요일(9월 27일 ~ 10월 3일) |
| 이번 달 | 1일 ~ 말일(10월 1일 ~ 31일) |
| 지난 달 | 지난달 1일 ~ 말일(9월 1일 ~ 30일) |
| 최근 7일 · 30일 | 오늘을 넣은 7일 · 30일(9월 26일 ~ 10월 2일) |
| 최근 3개월 · 6개월 · 1년 | 이번 달을 넣은 석 달 · 여섯 달 · 열두 달의 1일 ~ 이번 달 말일(8월 1일 ~ 10월 31일) |
| 올해 | 1월 1일 ~ 12월 31일 |
달력 단위다 — 끝은 그 단위의 마지막 날이고, 앞으로의 날(예정 거래 · 일정)도 들어간다. 주는 달력처럼 일요일부터다. 칩을 누르면 그 기간의 시작이 든 달로 옮긴다. 기간의 시작 · 끝이 고를 수 있는 범위 밖이거나 막힌 날이면 그 칩을 막는다 — 잘라서 넣지 않는다(이름이 같으면 같은 기간이어야 한다). 사이에 막힌 날이 있는 것은 괜찮다(손으로 고를 때와 같다).
연 · 월 옮기기
하루를 고르면 달력이 기본이다 — 요일 · 오늘과의 거리가 보인다. 몇 해 앞뒤의 먼 날도 이 길로 고른다 — 연 · 월 · 일 세 휠은 피한다(Wheel Picker 의 "휠을 쓰는 자리"). 제목을 누르면 요일 줄과 날짜 자리(요일 줄 + 6 × 칸 — 100% 336, 글이 크면 달력을 따라 늘어난다)에 연 | 월 휠이 뜬다(Wheel Picker medium · 7칸). 셰브론이 위로 돌고 이전 · 다음은 막힌다. 제목을 다시 누르면 고른 달로 옮긴다. 연의 범위는 자리마다 정하고(기본 오늘 ± 100년), 월은 돌아간다.
고를 수 없는 날
자리마다 고를 수 있는 범위가 다르다(환불일은 거래일 ~ 오늘, 할 일 마감은 오늘부터). 그 밖의 날은 막힌 날로 그린다 — 흐린 숫자 + 취소선, 키보드 초점은 간다. 이미 확정돼 바꿀 수 없는 시작일(진행 중인 기간 늘리기)은 읽기 전용 — 채움을 남겨 흐리게 그린다. 달력 전체가 보기 전용이면 날짜마다가 아니라 달력 전체를 읽기 전용으로 둔다.
여러 날
multiple 은 누를 때마다 고르고 풀린다 — 고른 날은 따로 그린 원이고 띠로 잇지 않는다. 최대 개수가 있으면 달력 위에 남은 개수를 보이고, 다 고르면 고르지 않은 날이 막힌 날(취소선)이 된다 — 고른 날은 풀 수 있다. 칸에는 가장 이른 날 + 나머지 개수("10월 14일 (수) 외 2개" — Select 여럿 고르기와 같은 꼴). 지금 쓰는 곳은 없다.
글
칸에 들어가는 값은 International Design(v106)의 표기다 — 올해는 "10월 15일 (목)", 다른 해는 "2027년 1월 3일 (일)", 기간은 "10월 1일~10월 31일"(물결표를 앞뒤에 붙여 쓴다 — International Design, 올해가 아닌 날이 끼면 양쪽에 연도). 시트 제목은 고를 값의 종류("날짜 선택" · "기간 선택"), 칸이 비면 placeholder 도 같은 말이다.
코드
레시피 recipes/shadcn/components/ui/date-picker.tsx 를 Input Button 이 연 시트 · 팝오버 안에 둔다. 값은 쓰는 쪽이 가진다 — 달력은 고르던 값(draft)을 보이고, "완료" 가 칸에 넣는다. 여는 자리는 useInputButtonSurface()(1280 미만 "sheet" · 이상 "popover")가 정한다. 아래 미리보기는 스펙 값으로 그린 모습이다.
날짜 하나 — "완료" 로 넣는다
이 창의 폭으로 연다 — 1280 미만은 아래 시트, 이상은 칸 아래 팝오버.
import { CalendarDays } from "lucide-react"
import { DatePicker, formatDateValue } from "@/components/ui/date-picker"
import { InputButton, useInputButtonSurface } from "@/components/ui/input-button"
const surface = useInputButtonSurface()
const [draft, setDraft] = React.useState(date)
<InputButton value={formatDateValue(date)} placeholder="날짜 선택" suffixIcon={<CalendarDays />} onClick={() => { setDraft(date); setOpen(true) }} />
// 열면 고른 날(없으면 오늘)로 초점을 옮긴다
<DatePicker selection="single" value={draft} onValueChange={setDraft} autoFocus />
<Button disabled={!draft} onClick={() => { setDate(draft); setOpen(false) }}>완료</Button>기간 — 칩 줄 · 이어지는 달 · 두 달
이 창의 폭으로 연다 — 1280 미만은 아래 시트, 이상은 칸 아래 팝오버. 기간은 시트에서 달이 위아래로 이어지고, 팝오버에서 두 달이 나란히 보인다.
// 이어지는 달은 시트 높이를 채운다(시트 h-[90dvh]), 두 달은 팝오버 폭 744 — PopoverContent className={DATE_PICKER_TWO_MONTHS_POPOVER}
<DatePicker
selection="range"
visibleRange={surface === "sheet" ? "continuous" : "twoMonths"}
autoFocus
presets={["thisWeek", "thisMonth", "lastMonth", "last3Months", "thisYear"]}
value={draft}
onValueChange={setDraft}
/>
<Button variant="neutralWeak" onClick={() => setDraft(undefined)}>초기화</Button>
<Button disabled={!draft?.end} onClick={apply}>완료</Button>고를 수 있는 범위 — 환불일
이 창의 폭으로 연다 — 1280 미만은 아래 시트, 이상은 칸 아래 팝오버.
<DatePicker selection="single" min={transactionDate} max={today} value={draft} onValueChange={setDraft} />Behavior
| 인터랙션 | 동작 |
|---|---|
| 날짜 누르기 | 하루 — 고른다(다시 눌러도 풀리지 않는다). 기간 — 위 "기간" 의 순서. 여러 날 — 고르고 풀린다. 칸 값은 "완료" 전까지 그대로 |
| 막힌 날 · 앞뒤 달 누르기 | 아무 일도 없다 |
| 이전 · 다음 | 한 달씩 — 6주라 높이는 그대로 |
| 제목 누르기 | 연 · 월 휠을 열고 닫는다. 닫으면 고른 달로 옮기고 그 달 1일이 다음 Tab 자리 |
| 빠른 기간 칩 | 그 기간을 칠하고 그 달로 옮긴다. 달력에서 다른 날을 누르면 칩 고름이 풀린다 |
| 열 때 | 고른 날(없으면 오늘)이 든 달 — 초점은 그 날짜로 간다(input-button.md) |
| 이어지는 달 스크롤 | 보이는 달이 바뀌면 그 달 1일이 다음 Tab 자리 |
키보드
WAI-ARIA Grid 다 — 날짜 하나만 Tab 순서에 들고 방향키로 옮긴다. 웹 · 앱이 같다 — 앱도 하드웨어 키보드 · 스위치 접근에서 아래 키를 받고, 열 때 초점은 고른 날(없으면 오늘)로 간다. 링은 키보드일 때만 그린다(2026-10-21 사용자 결정 — 03-C 4A).
| 키 | 동작 |
|---|---|
← → | 하루 전 · 뒤 |
↑ ↓ | 한 주 전 · 뒤 |
Home End | 그 주의 일요일 · 토요일 |
PageUp PageDown | 한 달 전 · 뒤(같은 날, 없으면 말일) — 두 달 보기는 두 달 |
Shift + PageUp PageDown | 한 해 전 · 뒤 |
Enter Space | 초점이 있는 날을 고른다(막힌 날은 무시) |
Tab | 달력 밖(다음 요소 — "완료")으로 |
보이는 달 밖으로 옮기면 달이 따라 넘어가고 "2026년 11월" 을 읽는다(두 달 보기는 "2026년 10월~2026년 11월").
Accessibility
| 기준 | 검증 |
|---|---|
| WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1) | 숫자 fg-neutral-muted 바탕 위 7.11 · 다크 6.67 · 기간 띠 위 6.58 · 5.93, 고른 날 16.41 · 13.42 ✓. 막힌 날 · 앞뒤 달은 누를 수 없는 표시라 기준 밖(막힌 날은 취소선이 함께 알린다) |
| WCAG 1.4.11 Non-text contrast(≥ 3:1) | 고른 날 짙은 원 ✓. 오늘의 옅은 원은 바탕과 1.08 · 1.13 — 그래서 숫자 굵기로도 알린다. 호버 원(투명)은 바탕과 1.15 · 1.77, 오늘 원과 1.06 · 1.57 로 갈린다. 초점 링 stroke-focus-ring ✓ |
| WCAG 1.4.1 Use of color | 오늘은 굵기, 막힌 날은 취소선 — 색만으로 알리지 않는다 ✓ |
| WCAG 2.1.1 Keyboard | 위 키보드 표 — 모든 날을 키로 고른다 ✓ |
| WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24) | 날짜 칸 44.6 × 48(360 화면) · 48 × 48(팝오버) ✓ · 이전 · 다음 40(누르는 영역 44) ✓ |
| WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44) | 날짜 칸 ✓(320 화면은 38.9 × 48 ⚠) · 이전 · 다음 44 ✓ |
| WCAG 4.1.3 Status messages | 보이는 달이 바뀌면 aria-live="polite" 로 "2026년 11월" 을 읽는다 ✓ |
| 앱(Flutter 3.44.9) | 표 · 줄 · 칸 · 열 머리 역할(table · row · cell · columnHeader)은 의미 트리에 두지만 Android · iOS 화면 읽기에는 전달되지 않는다(Flutter 가 역할을 버린다) — 날짜 칸은 이름("2026년 10월 15일 목요일" + ", 오늘") · 고름 상태로 알리고 그것으로 충분하다. 연 · 월 휠 칼럼은 Wheel Picker 의 앱 줄대로 spinbutton 역할 대신 이름 + 값 + 올리기 · 내리기(조절 가능)다 |
| ARIA | 달력 role="group" 이름 "날짜 선택"(또는 칸 라벨), 달마다 role="grid" + 제목으로 aria-labelledby, 기간 · 여러 날은 aria-multiselectable. 요일은 columnheader(이름 "일요일"). 날짜 버튼 이름 "2026년 10월 15일 목요일", 오늘 aria-current="date", 고른 날 · 기간 사이 칸 aria-selected, 막힌 날 · 읽기 전용 aria-disabled(읽기 전용은 이름 끝에 ", 읽기 전용 시작일"). 앞뒤 달은 숫자만 aria-hidden(칸은 남겨 요일 짝을 지킨다). 이전 · 다음 이름 "이전 달" · "다음 달", 제목 버튼 aria-expanded. 이름은 한국어 · 영어 두 벌(앱 언어를 따른다) |
Do / Don't
✅ Do
- 날짜 칸을 누르면 달력이 열리게 — 날짜를 치게 하지 않는다.
- 기간은 칸 하나에 "9월 28일~10월 6일", 달력 하나에서 시작 · 끝을 고른다.
- 최소 · 최대 기간 같은 제약은 고르기 전에 Callout 으로 알린다.
- 빠른 기간은 정해진 이름 · 범위 한 벌에서 고른다.
❌ Don't
- 날짜 칸을 치는 칸으로 두기(지금 세 제품 66곳).
- 고른 날 · 오늘을 브랜드 색으로 채우기 — 고른 날은 짙은 회색, 오늘은 옅은 원 + 굵기.
- 막힌 날을 흐림(불투명도)으로만 그리기 — 흐린 색 + 취소선.
- 같은 "이번 달" 을 화면마다 다른 기간으로 쓰기.
- 날짜를 누르는 순간 칸에 넣고 닫기 — "완료" 로 넣는다.
Specification
Base
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 너비width | 336px팝오버 — 칸 48 × 7. 시트는 시트 폭 − 좌우 24 · 24(360 화면 312 · 칸 44.6) |
| 배경background | bg-layer-floating#FFFFFF다크 #2D3346 | ||
presets | enabled | 아래 여백paddingBottom | spacing-x312px칩 줄 ↔ 머리 12 |
| 간격gap | spacing-between-chips8px칩 사이 8 — 시트는 한 줄 가로 스크롤(화면 끝까지), 팝오버는 줄바꿈 | ||
| 칩chip | Chip outlineStrong medium 36하나 고르기(라디오) — 고른 칩은 짙은 채움, 달력에서 다른 날을 누르면 풀린다. 이름 · 범위는 date-picker.md 의 '빠른 기간' | ||
header | enabled | 높이height | 48px최소 — 글자가 크면 그만큼만 늘어난다. 칸 · 머리 · 요일 줄 · 원 · 이전 · 다음은 px, 글자만 배율(SEED, 03-C 1B) |
title | enabled | 좌우 여백paddingX | spacing-x14px제목 글자는 머리 왼쪽에서 4 — 누르는 자리는 머리 높이 48 전체 |
| 간격gap | spacing-x14px글자 ↔ 셰브론 | ||
| 모서리radius | radius-r28px바탕이 없어 초점 링에만 보인다 | ||
| 글자·아이콘foreground | fg-neutral#1A1F2E다크 #F5F6FA | ||
| 글자typography | text-t516px / 22px | ||
| 굵기fontWeight | 700 | ||
titleIcon | enabled | 크기size | 20pxlucide chevron-down |
| 색color | fg-neutral#1A1F2E다크 #F5F6FA | ||
navButton | enabled | 크기size | 40pxButton ghost iconOnly medium 을 px 로 — 글자 크기를 따르지 않는다(03-C 1B). 둘 사이 0, 머리 오른쪽 끝, 누르는 영역 44 |
| 아이콘 크기iconSize | 18pxlucide chevron-left · chevron-right | ||
| 색color | fg-neutral#1A1F2E다크 #F5F6FA | ||
weekday | enabled | 높이height | 48px최소 — 글자가 크면 그만큼만 늘어난다(03-C 1B) |
| 글자·아이콘foreground | fg-neutral-subtle#62697A다크 #A2A8B7 | ||
| 글자typography | text-t414px / 19px | ||
| 굵기fontWeight | 500 | ||
cell | enabled | 높이height | 48px최소(SEED dateCell minHeight) — 글자가 크면 그만큼만 늘어난다(03-C 1B): max(48, 숫자 줄 높이 + 위아래 3 · 3) — 날짜 버튼 · 앞뒤 달 숫자가 위아래 3 을 가진다(200% 44 + 6 = 50 · 달력 396). 폭은 달력 폭 ÷ 7 — 칸 전체가 누르는 자리, 칸 사이 0 |
| 커서cursor | pointer | ||
| 주 수weeks | 6늘 6주 — 달을 넘겨도 머리 · 바닥이 움직이지 않는다(이어지는 달만 그 달의 주 수) | ||
dayVisual | enabled | 크기size | 42pxpx — 글자 크기를 따르지 않는다(03-C 1B, SEED). 칸 위 3 · 가로 가운데 — 원 · 띠끼리 6 떨어진다. 칸이 44 보다 좁으면(320 화면) 칸 폭 − 2 |
| 모서리radius | radius-full9999px(알약) | ||
| 배경background | transparent | ||
| 전환 대상transitionProperty | background-color | ||
| 전환 시간transitionDuration | motion-duration-color-transition150ms | ||
| 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
dayLabel | enabled | 글자·아이콘foreground | fg-neutral-muted#535866다크 #B7BDCC |
| 글자typography | text-t516px / 22px숫자 폭을 같게(tabular-nums) | ||
| 굵기fontWeight | 500 | ||
rangeBand | enabled | 높이height | 42px칸 위 3 — 원과 같은 높이(칸이 좁아 원이 줄면 띠도 같이). 시작 칸은 원 가운데부터 오른쪽, 끝 칸은 왼쪽부터 원 가운데까지, 사이 칸은 칸 폭 전체 — 줄 끝에서 둥글리지 않고 칸 끝에서 끊는다 |
| 배경background | bg-neutral-weak#F5F6FA다크 #353B4D | ||
monthLabel | enabled | 높이height | 48px최소 — 글자가 크면 그만큼만 늘어난다(03-C 1B) |
| 글자·아이콘foreground | fg-neutral#1A1F2E다크 #F5F6FA | ||
| 글자typography | text-t516px / 22px | ||
| 굵기fontWeight | 700 | ||
wheel | enabled | 높이height | 336px요일 줄 + 6주 자리를 덮는다 — 높이는 요일 줄 + 6 × 칸이라 글이 크면 달력을 따라 늘어난다(100% 요일 줄 48 + 6주 288 · 200% 48 + 50 × 6 → 348, 열고 닫아도 달력 높이가 그대로). Wheel Picker medium 44 × 7 = 308 을 가운데에(100% 위아래 14). 열린 동안 이전 · 다음은 막힌다 |
| 칼럼columns | 연 120 · 월 96연은 오른쪽 · 월은 왼쪽 정렬로 가운데에 모은다(SEED 와 같다) — 항목은 "2026년" · "10월" | ||
| 배경background | bg-layer-floating#FFFFFF다크 #2D3346 | ||
footer | enabled | 위 여백paddingTop | spacing-x416px팝오버 — 달력 아래 16, 버튼 small 36 오른쪽 맞춤. 시트는 Bottom Sheet 바닥(위 12 · 아래 16, large 48 — 하나면 폭 전체, 둘이면 반씩) |
dayVisual | hovered | 배경background | bg-neutral-weak-alpha-pressed#00000010다크 #FFFFFF2ESEED 투명 색 — 바탕 1.15 · 다크 1.77(2026-10-21 03-C 2B). 마우스(고운 포인터)만 — 축소 없음. 오늘(옅은 원) · 기간 사이 날(띠) 위에서는 불투명 bg-neutral-weak-pressed(투명 색을 띠에 얹으면 또 다른 색이 된다). 막힌 날 · 읽기 전용 · 앞뒤 달에는 없다 |
dayVisual | pressed | 배경background | bg-neutral-weak-alpha-pressed#00000010다크 #FFFFFF2E터치는 누르는 동안 — 오늘 · 기간 사이 날 위에서는 불투명 bg-neutral-weak-pressed. 날짜는 줄지 않는다(이전 · 다음은 Button 처럼 2px 축소) |
focusRing | focused | 링 두께width | 2px |
| 링 색color | stroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84 | ||
| 링 간격offset | -2px원 자리(42) 안쪽 — 칸이 아니라 원 둘레 | ||
dayVisual | today | 배경background | bg-neutral-weak#F5F6FA다크 #353B4D |
dayLabel | today | 글자·아이콘foreground | fg-neutral#1A1F2E다크 #F5F6FA |
| 굵기fontWeight | 700색이 아니라 굵기로도 알린다 — 고른 날 · 기간 안에서도 굵기는 남는다 | ||
dayVisual | selected | 배경background | bg-neutral-inverted#1A1F2E다크 #F5F6FA하루 · 여러 날의 고른 날, 기간의 시작 · 끝 — 오늘이어도 이 모양이 이긴다(굵기는 남는다) |
dayLabel | selected | 글자·아이콘foreground | fg-neutral-inverted#FFFFFF다크 #242938 |
dayVisual | inRange | 배경background | transparent띠가 바탕 — 오늘이어도 원은 그리지 않는다(굵기만 남는다) |
dayLabel | inRange | 글자·아이콘foreground | fg-neutral-muted#535866다크 #B7BDCC |
cell | outside | 커서cursor | default |
dayLabel | outside | 글자·아이콘foreground | fg-disabled#8A91A0다크 #838997앞뒤 달 날짜 — 보이기만 하고 누르지 못한다(숫자만 보조 기술에 숨기고 칸은 남겨 요일 짝을 지킨다). 기간 띠도 그리지 않는다. 다른 달은 이전 · 다음 · 화살표 키로 |
cell | disabled | 커서cursor | not-allowed |
dayLabel | disabled | 글자·아이콘foreground | fg-disabled#8A91A0다크 #838997 |
| 취소선textDecoration | line-through고를 수 없는 날 — 흐린 색과 함께 취소선. aria-disabled 라 키보드 초점은 간다 | ||
cell | readOnly | 커서cursor | default |
dayVisual | readOnly | 배경background | stroke-neutral-solid#767C8B다크 #838997확정된 시작일 — 채움은 남기고 톤을 낮춘다(SEED palette gray-600 의 역할 짝). 누르지 못하지만 초점은 간다 |
dayLabel | readOnly | 글자·아이콘foreground | fg-neutral-inverted#FFFFFF다크 #242938 |
visibleRange=twoMonths
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 너비width | 696px336 + 24 + 336 — 팝오버 폭 744(Popover 최대 480 의 예외) |
| 칸 간격columnGap | spacing-x624px두 달 사이 24 | ||
header | enabled | 높이height | 48px머리를 나누지 않는다 — 달 이름은 달마다 가운데, 이전 버튼은 첫 달 왼쪽 끝 · 다음 버튼은 둘째 달 오른쪽 끝(위 4) |
title | enabled | 글자typography | text-t516px / 22px달 이름은 누르지 않는다 — 연 · 월 휠이 없다 |
visibleRange=continuous
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 높이height | 시트에 맞춘다시트를 화면 높이의 90% 로 열고 머리 · 칩 줄 · 바닥을 뺀 높이 — 그 안에서 달이 위아래로 이어진다(스크롤바 숨김) |
header | enabled | 높이height | 0px머리 없음 — 스크롤이 달을 넘긴다. 연 · 월 휠도 없다 |
weekday | enabled | 자리position | sticky맨 위에 붙는다 — 달마다의 요일 줄은 없다 |
cell | enabled | 주 수weeks | 그 달의 주 수4 ~ 6 — 앞뒤 달 날짜는 비운다(달이 이어져 다음 달이 바로 아래에 있다) |
monthLabel | enabled | 좌우 여백paddingX | spacing-x14px왼쪽 정렬, 달 아래 16 |
fog | enabled | 높이height | 96px아래 끝 — 투명에서 바탕색으로 |
SEED 와 다른 점
- 오늘은 옅은 원 + 숫자 700 — groo 옅은 원(
bg-neutral-weak)은 바탕과 1.08:1 이라 굵기로도 알린다. 기간 안 · 고른 날이어도 굵기는 남는다(SEED 는 옅은 원만, 기간 안에서는 사라진다). - 앞뒤 달을 흐리게 채우고 늘 6주 — SEED 는 비우고 4 ~ 6주라 달을 넘길 때 시트 머리 · 바닥이 48 · 96 씩 움직인다. 흐린 날은 누르지 못하고 보조 기술에 숨긴다.
- 팝오버 달력 336(칸 48 × 7) · 두 달 696 — SEED 는 부모 폭.
- 좁은 칸에서 원을 줄인다 — 칸이 44 보다 좁으면 원은 칸 폭 − 2(SEED 는 320 화면에서 이웃 원이 겹친다).
- 색은 groo 역할 짝 — 고른 날
bg-neutral-inverted, 읽기 전용 시작일stroke-neutral-solid(SEED palette gray-600), 기간 사이 숫자fg-neutral-muted(SEED 코드와 같다 — 그림은 fg-neutral). - 호버 · 누름은 SEED 와 같은 투명 색(
bg-neutral-weak-alpha-pressed) — 오늘 · 기간 사이 날 위만 불투명bg-neutral-weak-pressed다(투명 색을 띠에 얹으면 또 다른 색이 된다). - 빠른 기간 칩 줄 — SEED 에는 가이드가 없다("오늘로 이동" 예제뿐).
- Week 보기는 두지 않는다 — 쓰는 곳이 없다.
- 펼친 달력(Inline)은 SEED 와 같게 둔다 — 지금 쓰는 곳은 없다 —
multiple과 같은 결이다(2026-10-22 1C). - 열 때 초점은 고른 날(없으면 오늘) — SEED 는 정하지 않았다(Tab 자리는 고른 날, 없으면 1일).
Migration notes
2026-10-22 — 어디에 놓나(펼친 달력) · 먼 날은 달력 제목
SEED Date Picker 의 Layout 셋(Inline · Sheet · Popover) 가운데 Inline 만 스펙에 없었다 — 10-01 · 10-03 비교 페이지에 오른 적이 없어 거절이 아니라 빠짐이다. "어디에 놓나" 상황 표 · 견주기 표에 펼친 달력을 넣고 "지금 쓰는 곳은 없다" 를 함께 적었다(1C — 10-03 12 에서 쓰는 곳 없는 multiple · 읽기 전용도 "SEED 와 같게 둔다" 를 골랐다). 날짜 + 시각은 결정(두 칸 나란히) 그대로 + "날짜가 주된 단계면 달력은 펼치고 시각은 칸"(2B — Time Picker). 하루는 달력이 기본, 먼 날은 제목의 연 · 월 휠, 연 · 월 · 일 세 휠은 "피한다"(3B — Wheel Picker). 사용자 결정 — https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L. 고르지 않은 안: 말 없음(1A) · 두지 않는다고 적기(1B) · 결정 문장만(2A) · 세 휠 Don't 그대로(3A) · 먼 날 칸은 세 휠 허용(3C). 값 변경 없음. 웹 · 앱 할 일 없음 — 펼친 달력을 쓰는 화면이 생기면 그 화면이 시트 없이 달력을 놓는다.
2026-10-22 — 큰 글자 칸 · 휠 자리 · 앱 접근성(I-04 검수)
- 큰 글자 칸 — 사용자가 본 그림(1B — 칸 = max(48, 줄 높이 + 6), 200% 396)을 레시피가 만들지 못했다: 칸
min-h-[48px]에 날짜 버튼p-0이라 200% 에서 칸 48 · 달력 384 였다. 레시피 날짜 버튼(DAY)과 앞뒤 달 숫자(OUTSIDE)에 위아래 3(py-[3px])을 두고, 이어지는 달은 이름 줄 · 칸 · 요일 줄을 숨은 견본으로 재서 달 높이를 셈한다(배율이 바뀌면 다시 재고 보던 달로). 100% 값은 그대로다. 웹은 레시피 그대로 따라 고친다(date-picker-variantsDAY · OUTSIDE · 이어지는 달 높이 셈). 앱은 이미 이 셈이다. - 휠 자리 —
wheel.height336 고정을 "요일 줄 + 6 × 칸"(100% 336 · 200% 348)으로. 레시피WHEEL높이는calc(max(48px, var(--text-t4--line-height)) + 6 * max(48px, calc(var(--text-t5--line-height) + 6px)))다. 앱은 이미 이렇다(웹 · 레시피 336 고정이 200% 에서 휠을 열 때 달력을 12 줄였다). - 앱 접근성 — Flutter 3.44.9 는 spinButton 역할을 구현하지 않았고(디버그에서 오류), 표 · 칸 역할을 Android · iOS 에 전달하지 않는다. 휠은 이름 + 값 + 올리기 · 내리기, 날짜 칸은 이름으로 알린다(Accessibility 의 앱 줄).
2026-10-21 — 큰 글자 · 호버 · 앱 키 · 캘린더 화면(03-C)
구현 조사 03-C(2026-10-16 ~ 17)를 보고 사용자가 정했다(비교 페이지 https://claude.ai/artifact/3nWUZCYpPh2Cg3uXc3DKtp).
- 큰 글자(1B) — 글자만 배율, 칸 · 머리 · 요일 줄은 최소 48 · 원 42 · 이전 · 다음 40 은 px(SEED). 웹은 rem 이던 칸이 200% 에서 96 으로 커져 시트를 넘고 제목이 "202…" 로 잘렸다 — 레시피
min-h-[48px]· 이전 · 다음h-[40px] w-[40px]. 앱은 칸 48 고정을 최소 48 로. 거절: 웹 rem 그대로 · 달력 글자 모두 -static. - 호버 · 누름 원(2B) — 불투명
bg-layer-floating-pressed가 다크에서 오늘 원(bg-neutral-weak)과 같은 색이었다. SEED 투명 색bg-neutral-weak-alpha-pressed로(추천 A "늘 bg-neutral-weak-pressed" 가 아니다). 오늘 · 기간 위는 그대로 불투명bg-neutral-weak-pressed. - 앱 방향키(4A) — 앱도 웹과 같은 키 · 열 때 초점 · 키보드일 때만 링. 앱
PDatePicker에 Focus · 키 처리와 테스트를 더한다(이어지는 달은 스크롤 위치를 따라간다). - 캘린더 화면 격자(5A) — 피커가 아니지만 같은 규칙을 쓴다: 이웃 달 날짜는 숫자만
fg-disabled(일정 · 금액은 이번 달과 같게 읽힌다), 꺼진 캘린더 줄은 체크로만 알리고 흐리게 하지 않는다(불투명도 금지 — DESIGN State). 웹calendar-month-view.tsx:206 · 233 · 252·calendar-source-toggle.tsx:138 · 169의 불투명도를 걷는다.
2026-10-03 — SEED Date Picker 로 새로 둔다(옛 Calendar 를 대신)
사용자가 비교 페이지에서 정했다 — 달력 모양 SEED(칸 48 · 원 42 · 숫자 16), 오늘은 옅은 원 + 굵기, 앞뒤 달은 흐리게 채우고 6주(누르지 못함), 연 · 월 휠, 기간은 달력 하나(시트 이어지는 달 · 팝오버 두 달), 빠른 기간 칩 한 벌(달력 단위), 여러 날 · 읽기 전용도 둔다. 옛 Calendar(react-day-picker · 원 40 · 고른 날 브랜드 채움 · 오늘 2px 테두리 · 앞뒤 달 tertiary · 막힌 날 불투명도 0.5)는 걷었다 — 옛 스펙은 calendar.history/v-pre-seed-date.*.
제품은 앱 적용 단계에서 옮긴다(2026-10-02 조사 — Desk 웹 · HR 은 크로미움에 띄워 쟀고, Desk 앱은 위젯 테스트로 쟀다).
- 세 제품 모두 — 날짜 칸이 치는 칸이다(Desk 웹 14 · 앱 19 · HR 19, 네이티브 date 6 따로). 달력은 누르면 바로 들어가고 닫힌다. 기간 달력은 한 곳도 없다(두 칸).
- Desk 웹 — 앞뒤 달이 이번 달과 같은 색, 다크에서 고른 날 · 오늘 테두리 1.96:1, 이름이 영어("Go to the Previous Month" · "Today, …"), 월 셀렉트가 브라우저 언어(Jan…Dec), 폰에서도 팝오버(시트 위로 뜬다), 빠른 기간 세 벌이 같은 이름에 다른 기간.
- Desk 앱 — 다크에서 고른 날 2.75:1, 낭독기에 날짜가 "8" 로만 읽힌다(버튼 아님 · 이전 · 다음 이름 없음), 날짜 상한이 2030-01-01(일정 · 할 일 · 저축 · 더치페이)로 박혀 있다, 일정 화면이 2020 ~ 2030 밖에서 깨진다.
- HR 웹 — 칸에 치면 값이 깨진다("2" → "2001-02-01", 업무 보고 필터는 지울 수 없다, 일정 종료일은 칠 때마다 토스트), 한국어 화면에 영어 달력, 오늘 회색 사각 1.06:1, UTC 로 읽어 하루 전을 칠한다.
- 앱 적용 때 화면마다 정할 자리 — 자리별 고를 수 있는 범위(거래 · 일정의 미래, 할 일 · 저축 상한), HR 보기용 작은 달력(일 보기 옆 · 근무 미입력 위젯), 일정 화면 격자(피커가 아니다 — 오늘 = 채운 원은 캘린더 화면 차례에), 통계의 월 · 분기 · 년 보기.
Content Placeholder
이미지가 없거나 불러오지 못했을 때 그 자리를 채우는 대체 그림 — 옅은 면 가운데에 무엇이 없는지 말하는 선 아이콘. 카드 그림 · 혜택 그림 · 첨부 사진처럼 이미지가 들어갈 틀이 비었을 때 쓴다 — 보통은 Image Frame 이 제 대체 그림으로 그린다. 불러오는 동안에는 같은 모서리의 Skeleton 이고, 사람의 사진이 없으면 Avatar 의 이니셜, 물건 타일의 그림이 없으면 Logo Tile 의 첫 글자다.
Dialog
1280 이상에서 화면 정중앙에 뜨는 대화상자. 입력 폼 · 상세처럼 지금 화면을 떠나지 않고 할 일을 띄운다 — 같은 내용을 1280 미만에서는 Bottom Sheet 로 띄운다(한 부품이 폭으로 바꾼다 — Responsive Dialog). 되돌릴 수 없는 확인은 Alert Dialog, 트리거에 붙는 짧은 내용은 Popover, 화면 높이를 넘는 긴 내용은 페이지다.