# Date Picker
URL: /react/components/date-picker
Source: https://github.com/daangn/seed-design/blob/dev/docs/content/react/components/date-picker.mdx
달력에서 하나의 날짜, 날짜 범위 또는 여러 날짜를 선택하는 컴포넌트입니다.
사용 가능 버전: @seed-design/react@2.2.0, @seed-design/css@2.4.0
## Preview
```tsx
import { Box, DatePicker } from "@seed-design/react";
export default function DatePickerPreview() {
return (
);
}
```
## Installation
- npm: npm install @seed-design/react @seed-design/css
- pnpm: pnpm add @seed-design/react @seed-design/css
- yarn: yarn add @seed-design/react @seed-design/css
- bun: bun add @seed-design/react @seed-design/css
## Props
### DatePicker
### TwoMonthDatePicker
### WeekDatePicker
### ContinuousDatePicker
## Examples
### 레이아웃
Date Picker는 레이아웃에 따라 다음 공개 컴포넌트로 나뉩니다.
- `DatePicker`: 현재 월의 실제 주 수만 렌더링합니다.
- `TwoMonthDatePicker`: 연속한 두 달을 가로로 표시합니다. 이전·다음 버튼은 한 달씩 이동합니다.
- `WeekDatePicker`: 한 주만 표시하며 이전·다음 버튼은 한 주씩 이동합니다.
- `ContinuousDatePicker`: `monthRange` 안의 월을 세로로 가상화합니다. `monthRange`를 생략하면 `yearRange`를 월 범위로 변환합니다. `height`, `minHeight`, `maxHeight` 중 하나를 반드시 지정합니다.
한 달과 두 달 레이아웃은 항상 6주 높이를 확보하지 않고 각 월에 필요한 4~6주만 렌더링합니다.
Date Picker는 자체적으로 고정 폭이나 최대 폭을 제한하지 않고 부모 컨테이너의 너비를 채웁니다. 한 달과 두 달 레이아웃에 필요한 가로 크기는 사용하는 화면에서 적절한 너비의 컨테이너로 감싸서 결정하세요.
```tsx
"use client";
import {
Box,
ContinuousDatePicker,
DatePicker,
TwoMonthDatePicker,
VStack,
WeekDatePicker,
} from "@seed-design/react";
import { SegmentedControl, SegmentedControlItem } from "seed-design/ui/segmented-control";
import * as React from "react";
const today = { year: 2026, month: 7, day: 30 };
const layoutOptions = [
{ value: "month", label: "한 달" },
{ value: "twoMonths", label: "두 달" },
{ value: "week", label: "한 주" },
{ value: "continuous", label: "연속" },
] as const;
type DatePickerLayout = (typeof layoutOptions)[number]["value"];
export default function DatePickerVisibleRanges() {
const [layout, setLayout] = React.useState("month");
const picker = (() => {
switch (layout) {
case "twoMonths":
return ;
case "week":
return ;
case "continuous":
return (
);
default:
return ;
}
})();
return (
setLayout(value as DatePickerLayout)}
>
{layoutOptions.map((option) => (
{option.label}
))}
{picker}
);
}
```
### 연속 달력의 노출 월 제한
`ContinuousDatePicker`의 `monthRange`로 스크롤해서 볼 수 있는 첫 월과 마지막 월을 제한할 수 있습니다. 양끝 월을 모두 포함합니다. `yearRange`만 전달하면 시작 연도의 1월부터 종료 연도의 12월까지로 변환합니다. 두 prop을 함께 사용하면 `monthRange`는 `yearRange` 안에 있어야 합니다.
`monthRange`는 노출되는 월을 정하고 `constraints`는 날짜별 선택 가능 여부를 정합니다. 아래 예시는 12월 15일부터 30일 뒤인 1월 14일까지만 선택할 수 있게 하면서, 스크롤 범위도 12월과 1월로 제한합니다.
```tsx
"use client";
import { Box, ContinuousDatePicker, dateOnOrAfter, dateOnOrBefore } from "@seed-design/react";
const today = { year: 2026, month: 12, day: 15 };
const thirtyDaysLater = { year: 2027, month: 1, day: 14 };
export default function ContinuousDatePickerMonthRange() {
return (
);
}
```
### 선택 방식
`selectionMode`에 따라 하나의 날짜(`single`), 날짜 범위(`range`), 여러 날짜(`multiple`)를 선택합니다. 기본값은 `single`입니다.
- `single`의 값은 `DatePickerDate`입니다.
- `range`는 시작일을 선택한 직후 `{ start }`, 종료일까지 선택하면 `{ start, end }`를 반환합니다.
- `multiple`은 중복 없는 날짜 배열을 항상 오름차순으로 반환합니다.
```tsx
"use client";
import {
Box,
DatePicker,
Text,
VStack,
type DatePickerDate,
type DatePickerRangeValue,
} from "@seed-design/react";
import * as React from "react";
const today = { year: 2026, month: 7, day: 30 };
export default function DatePickerSelectionModes() {
const [single, setSingle] = React.useState(today);
const [range, setRange] = React.useState({
start: { year: 2026, month: 7, day: 10 },
end: { year: 2026, month: 7, day: 13 },
});
const [multiple, setMultiple] = React.useState([
{ year: 2026, month: 7, day: 7 },
{ year: 2026, month: 7, day: 14 },
]);
return (
Single
Range
Multiple
);
}
```
### 날짜 선택 제약 조건
`constraints`는 후보 날짜를 선택할 수 있는지 판단하는 함수 배열입니다. 모든 함수가 `true`를 반환해야 선택할 수 있습니다. 직접 함수를 작성하거나 다음 helper를 조합할 수 있습니다.
- `dateOnOrAfter`, `dateOnOrBefore`: 선택 가능한 날짜의 양끝을 포함해 제한합니다.
- `excludeDates`: 예약 완료일, 휴무일처럼 선택할 수 없는 날짜를 제외합니다. Range에서는 기본적으로 시작일부터 후보 종료일까지의 전체 구간을 검사합니다.
- `rangeDayCountAtLeast`, `rangeDayCountAtMost`: 시작일과 종료일을 모두 포함한 날짜 수를 제한합니다.
- `maxSelectionCount`: Multiple에서 선택 가능한 최대 개수를 제한합니다. 이미 선택한 날짜의 해제는 허용합니다.
조건을 만족하지 않는 날짜는 키보드로 포커스할 수 있지만 `aria-disabled` 상태이며 선택할 수 없습니다.
아래 예시는 체크인·체크아웃을 포함한 선택 구간을 1~14일로 제한해 당일치기를 허용하고, 예약 완료일이 포함된 구간은 선택할 수 없도록 구성합니다.
```tsx
"use client";
import {
Box,
Text,
TwoMonthDatePicker,
VStack,
excludeDates,
rangeDayCountAtLeast,
rangeDayCountAtMost,
type DatePickerDate,
type DatePickerRangeValue,
} from "@seed-design/react";
import * as React from "react";
const bookedDateKeys = new Set(["2026-07-18", "2026-07-19", "2026-07-25"]);
function toDateKey(date: DatePickerDate) {
return `${date.year}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`;
}
const constraints = [
rangeDayCountAtLeast(1),
rangeDayCountAtMost(14),
excludeDates((date) => bookedDateKeys.has(toDateKey(date))),
];
export default function DatePickerReservation() {
const [value, setValue] = React.useState({
start: { year: 2026, month: 7, day: 10 },
});
return (
체크인 {toDateKey(value.start)}
{value.end ? ` · 체크아웃 ${toDateKey(value.end)}` : " · 체크아웃을 선택하세요"}
선택 구간은 체크인·체크아웃을 포함해 1~14일이어야 합니다.
예약 완료일(7월 18일·19일·25일)이 포함된 구간은 선택할 수 없습니다.
);
}
```
### 시작일을 유지하고 종료일만 변경
이미 시작된 기간을 수정할 때는 `selectionMode="range"`와 `rangeStartReadOnly`를 함께 사용합니다. 기존 시작일은 선택 상태로 표시되지만 변경할 수 없고, 시작일보다 늦은 날짜만 새 종료일로 선택할 수 있습니다.
`rangeStartReadOnly`에는 시작일을 포함한 `value` 또는 `defaultValue`가 필요합니다. 제어 방식으로 사용할 때 외부에서 전달하는 시작일이 바뀌면 Date Picker에도 새 값이 반영됩니다.
아래 광고 기간 예시는 시작일이 8월 7일이고 오늘이 8월 10일인 상황입니다. `dateOnOrAfter`를 함께 사용해 이미 지난 8일과 9일은 새 종료일로 선택할 수 없도록 제한합니다.
```tsx
"use client";
import {
Box,
DatePicker,
Text,
VStack,
dateOnOrAfter,
type DatePickerRangeValue,
} from "@seed-design/react";
import * as React from "react";
const today = { year: 2026, month: 8, day: 10 } as const;
const initialValue: DatePickerRangeValue = {
start: { year: 2026, month: 8, day: 7 },
end: { year: 2026, month: 8, day: 14 },
};
function formatDate(date: DatePickerRangeValue["start"]) {
return `${date.year}.${date.month}.${date.day}`;
}
export default function DatePickerReadOnlyRangeStart() {
const [value, setValue] = React.useState(initialValue);
return (
광고 기간 {formatDate(value.start)}
{value.end ? ` ~ ${formatDate(value.end)}` : ""}
);
}
```
### 날짜 셀에 추가 정보 표시
기본 날짜 숫자를 유지하면서 가격, 예약 가능 여부, 혼잡도 같은 정보를 추가할 때는 `renderDateCellSupplement`를 우선 사용하세요. 함수에는 날짜와 포맷된 날짜 숫자, 선택·오늘·범위·사용 불가·포커스 상태가 전달됩니다.
`renderDateCellSupplement`로 표현할 수 없어 날짜 숫자를 포함한 내부 콘텐츠 전체를 직접 구성해야 할 때만 `renderDateCellContent`를 사용합니다. 이 prop은 일반적인 확장 지점이 아니라 저수준 이스케이프 해치이며, 두 prop은 동시에 사용할 수 없습니다.
컴포넌트는 날짜 셀의 DOM, ARIA, 이벤트, 상태 배경을 계속 소유합니다. 따라서 두 함수에서는 버튼이나 `gridcell`을 만들지 말고 콘텐츠만 반환합니다. 각 주의 셀 높이는 가장 높은 콘텐츠에 맞춰 늘어납니다. 좁은 화면에서 콘텐츠가 가로로 넘치는 경우에는 렌더 함수에서 `maxLines` 같은 말줄임 정책을 직접 적용합니다.
```tsx
"use client";
import { Box, DatePicker, Text } from "@seed-design/react";
const prices = new Map([
[10, "12만원"],
[11, "9만원"],
[12, "11만원"],
[14, "8만원"],
]);
export default function DatePickerCustomCell() {
return (
(
{isUnavailable ? "선택 불가" : (prices.get(date.day) ?? "예약 가능")}
)}
constraints={[(date) => date.day !== 13]}
/>
);
}
```
### Time Picker와 조합
Date Picker는 시간과 시간대를 해석하지 않는 달력 날짜인 `DatePickerDate`를 사용합니다. Time Picker의 `TimePickerValue`와 별도로 상태를 관리한 뒤, 서버 요청이나 도메인 모델 경계에서 두 값을 합칩니다.
시간대가 필요한 서비스는 이 시점에 `CalendarDateTime`, `ZonedDateTime` 또는 서비스의 날짜·시간 DTO로 변환하세요. Date Picker 자체에 `CalendarDateTime`을 전달하면 날짜 선택 UI가 불필요하게 시간대 정책에 결합되므로 지원하지 않습니다.
```tsx
"use client";
import {
Box,
DatePicker,
Text,
TimePicker,
VStack,
type DatePickerDate,
type TimePickerValue,
} from "@seed-design/react";
import * as React from "react";
const TODAY: DatePickerDate = { year: 2026, month: 7, day: 30 };
export default function DatePickerDateAndTime() {
const [date, setDate] = React.useState(TODAY);
const [time, setTime] = React.useState({ hour: 14, minute: 30 });
return (
{date.year}.{date.month}.{date.day}. {String(time.hour).padStart(2, "0")}:
{String(time.minute).padStart(2, "0")}
);
}
```
### 날짜로 이동하고 포커스하기
`viewDate`, `defaultViewDate`, `onViewDateChange`는 현재 표시하는 월·주·스크롤 기준점을 제어합니다. Month에서는 월의 첫날로 정규화되므로 특정 날짜 셀을 이동 대상으로 지정할 때는 `actionsRef`를 사용합니다.
- `navigateToDate(date)`: 날짜가 보이도록 이동하고 다음 Tab 진입점을 갱신하지만 현재 DOM 포커스는 유지합니다. 외부의 “오늘” 버튼에는 이 action을 권장합니다.
- `focusDate(date)`: 날짜가 보이도록 이동한 뒤 날짜 셀에 실제 DOM 포커스를 둡니다. 키보드 단축키나 사용자를 명시적으로 그리드 안으로 이동시키는 동작에 사용합니다.
```tsx
"use client";
import {
ActionButton,
Box,
DatePicker,
HStack,
type DatePickerActions,
type DatePickerDate,
} from "@seed-design/react";
import * as React from "react";
const today: DatePickerDate = { year: 2026, month: 7, day: 30 };
export default function DatePickerNavigationActions() {
const actionsRef = React.useRef(null);
return (
actionsRef.current?.navigateToDate(today)}
>
오늘로 이동
actionsRef.current?.focusDate(today)}>
오늘에 포커스
);
}
```
### 월·연도 이동
제목을 누르면 Time Picker와 같은 Wheel Picker로 전환합니다. 연도 휠은 반복하지 않고 `yearRange` 안에서 이동하며, 월 휠은 반복합니다. `yearRange`의 기본값은 `today`를 기준으로 앞뒤 100년입니다.
Month 계열의 표시 기준 날짜는 월의 첫날, Week는 locale의 주 시작일로 정규화합니다.
### Locale
기본 locale은 `ko-KR`입니다. 월·요일·날짜·숫자 표기와 주 시작일은 locale을 따릅니다. `weekStartsOn`을 전달하면 주 시작일만 재정의합니다. RTL locale에서는 좌우 방향키와 이전·다음 아이콘 방향도 반전됩니다.
루트, 이전·다음 버튼과 연도·월 휠의 기본 접근성 이름은 한국어와 영어로 제공됩니다. 제품 맥락에 맞는 이름이 필요하면 `ariaLabels`로 재정의합니다.
### Accessibility
Date Picker는 WAI-ARIA Grid 패턴을 사용합니다. 하나의 날짜만 `tab` 순서에 들어가며 방향키로 날짜를 이동합니다.
- `ArrowLeft`, `ArrowRight`: 하루 전·후
- `ArrowUp`, `ArrowDown`: 일주일 전·후
- `Home`, `End`: 현재 주의 시작·끝
- `PageUp`, `PageDown`: 현재 표시 단위의 이전·다음
- `Shift + PageUp`, `Shift + PageDown`: 일 년 전·후
- `Enter`, `Space`: 포커스한 날짜 선택
화면에 Date Picker의 이름을 나타내는 요소가 있다면 `aria-labelledby`로 연결할 수 있습니다. 그렇지 않으면 locale에 따라 `"날짜 선택"` 또는 `"Select date"`가 사용됩니다.