Datepicker
달력에서 날짜를 고른다. 내부는 react-day-picker 이고 값은 Date 객체로 주고받는다. 기간은 DateRangePicker, 여러 날짜는 DateMultiplePicker 다.
import { Datepicker, DateRangePicker, DateMultiplePicker } from "@nui-kit/react";
// 서브패스로 좁힐 때
import { Datepicker } from "@nui-kit/react/datepicker";
import "@nui-kit/react/styles/datepicker.css"; // 온디맨드일 때import { Datepicker, DateRangePicker } from "@nui-kit/react";
<Datepicker selected={date} onSelectedChange={setDate} />
<DateRangePicker selected={range} onSelectedChange={setRange} />selected 와 onSelectedChange 를 소비자가 소유한다. react-hook-form 을 쓴다면 @nui-kit/react/rhf 의 RHFDatepicker 계열을 쓴다.직접 입력
Datepicker 와 DateRangePicker 는 입력창에 날짜를 직접 칠 수 있다. 달력이 있어도 입력 필드를 읽기 전용으로 만들지 않는다는 KRDS 기준(가이드 675쪽)을 따른다. 형식은 displayFormat (기본 yyyy.MM.dd), 기간은 2026.09.01 - 2026.09.05 처럼 앞뒤에 공백을 둔 대시로 잇는다.
| 이렇게 치면 | 이렇게 된다 |
|---|---|
2026.9.5 | 값으로 읽고, 입력창을 벗어나면 2026.09.05 로 정리한다 |
| 읽을 수 없는 글자 · 없는 날짜(2026.02.31) · 절반만 친 기간 | 입력창을 벗어나는 순간 치기 전 값으로 되돌린다. 에러 메시지는 띄우지 않는다 — 검증은 소비자 몫이다 |
| 달력이 막아 둔 날짜 · 이동할 수 없는 연도 | 받지 않는다. dayPickerProps 의 disabled · startMonth · endMonth 를 타이핑에도 똑같이 적용한다 |
| 입력창을 비움 | 값이 undefined 가 된다 |
치는 동안 달력은 그 날짜의 달로 따라 이동한다. 예전처럼 달력으로만 값을 받고 싶으면 isTextInputBlocked 를 준다. DateMultiplePicker 는 아직 읽기 전용이다 — 날짜 목록의 구분자 규칙이 따로 필요해 다음 단계로 미뤘다.
형식 안내는 infoMessage 로 적는다. 플레이스홀더만으로 형식을 알리지 않는다 — 값을 치기 시작하면 사라지기 때문이다.
dayPickerProps.min 기본값 1). 하루도 허용하려면 dayPickerProps={{ min: 0 }} 를 넘긴다.기본 — 날짜 하나
입력창을 클릭하거나 캘린더 버튼을 누르면 달력이 열린다. 날짜를 고르면 자동으로 닫힌다.
현재 값: 없음
<Datepicker selected={date} onSelectedChange={setDate} />선택하면 닫힌다 (shouldCloseOnSelect 기본값)
기간 선택
시작일과 종료일을 차례로 고른다. 둘 다 정해지기 전까지는 onSelectedChange 가 undefined 를 넘긴다 — 불완전한 기간이 폼에 들어가지 않는다.
현재 값: 없음
<DateRangePicker selected={range} onSelectedChange={setRange} />from → to 순서로 선택
여러 날짜
날짜를 여러 개 고른다. 선택할 때마다 달력이 닫히면 불편하므로 열린 상태를 유지한다.
현재 값: 없음
<DateMultiplePicker selected={dates} onSelectedChange={setDates} />선택해도 닫히지 않는다
선택 가능 범위 제한
dayPickerProps 로 react-day-picker 에 그대로 전달한다. 오늘 이전 날짜를 막은 예다.
dayPickerProps={{ disabled: { before: new Date() } }}dayPickerProps.disabled 로 과거 차단
상태
<Datepicker selected={date} errorMessage="날짜를 선택해주세요" />disabled
readOnly — 값은 보이지만 달력이 열리지 않는다
errorMessage — 아이콘과 텍스트를 함께 표시하고 aria-describedby 로 연결한다
잘리는 상자 안에서 — hasPortal
달력은 기본적으로 제자리에 뜬다. 조상에 overflow: hidden 이 있으면 잘려서 날짜를 고를 수 없다. 카드나 팝업 안에 넣을 때 hasPortal 을 켜면 달력이 body 로 나가 잘리지 않는다. Tooltip · Select 의 같은 이름 prop 과 한 규칙이다.
기본상자에 잘린다hasPortal상자를 벗어난다<Datepicker hasPortal /> // 잘리는 상자 안에서Field 와 함께
영업일만 선택할 수 있습니다.
react-hook-form 연동
@nui-kit/react/rhf 의 RHFDatepicker · RHFDateRangePicker · RHFDateMultiplePicker 는 control 만 넘기면 값과 에러를 스스로 소유한다. selected · name · onBlur 는 타입에서 제외되어 중복 소유가 생기지 않는다.
mode: "onChange"
스타일 커스터마이징
공개 CSS 변수로 달력 치수와 팝업 외형을 조정한다.
색은 컴포넌트별로 열지 않는다. 한 곳만 바꾸려면 className 을, 화면 전체를 바꾸려면 브랜드 프리셋을 쓴다.
| 컴포넌트 | 변수 | 기본값 | 비고 |
|---|---|---|---|
| Datepicker 계열 | --nui-datepicker--border-width | var(--nui-border-width-1) | — · 3자리를 함께 움직인다 |
| --nui-datepicker--day-button-size | var(--nui-size-control-md) | — | |
| --nui-datepicker--day-radius | var(--nui-radius-1_5) | — | |
| --nui-datepicker--day-size | var(--nui-size-control-option) | — | |
| --nui-datepicker--dropdown-radius | var(--nui-radius-3) | — |
react-day-picker 의 기본 CSS 는 불러올 필요가 없다. 이 컴포넌트는 라이브러리의 classNames 를 통째로 nui-daypicker__* 로 갈아끼운 뒤 우리 CSS 로 그린다. 그래서 소비자 프로젝트가 같은 라이브러리를 따로 쓰더라도 서로 간섭하지 않는다. 달력 세부 스타일을 직접 손보려면 nui-daypicker__day 처럼 우리 클래스를 대상으로 하면 된다.접근성
- 입력창에
aria-haspopup="dialog"·aria-expanded·aria-controls가 붙고, 팝업은role="dialog"로 연결된다 - 키보드로 연다 — Enter Space ↓, Esc 로 닫는다. 바깥을 클릭해도 닫힌다
- 달력 안에서는
react-day-picker의 키보드 탐색을 그대로 쓴다 (방향키로 날짜 이동, Enter 로 선택) - 토요일·일요일은 색으로만 구분하지 않는다 — 요일 헤더가 항상 함께 보인다
readOnly는 값을 보여주되 달력을 열지 않는다.disabled는 포커스도 받지 않는다prefers-reduced-motion에서 팝업 애니메이션이 꺼진다- 날짜 셀과 이전/다음 버튼은 누르는 범위가 44px 이다. 날짜 버튼은 36px, 화살표는 32px 로 보이고 히트만 넓혔다. 셀 크기는
--nui-datepicker--day-size로 바꿀 수 있다
API
세 컴포넌트는 selected 의 타입만 다르고 나머지는 같다. dayPickerProps 로 react-day-picker 의 설정(disabled, startMonth, locale 등)을 그대로 전달한다.
Datepicker
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
calendarButtonTitle | string | — | — |
calendarLabel | string | — | 캘린더 팝업의 접근 이름. i18n 을 위해 열어둔다. |
className | string | undefined | — | — |
clearButtonTitle | string | — | 지우기 버튼의 접근 이름. 소비자의 어휘·언어로 바꿀 수 있어야 한다 (a11y.md §9) |
dayPickerProps | DatepickerDayPickerProps<TDayPickerProps> | — | — |
defaultIsCalendarOpen | boolean | — | — |
disabled | boolean | — | — |
displayFormat | string | — | — |
dropdownClassName | string | — | — |
errorMessage | string | — | — |
formatDisplayValue | DatepickerBaseSingleProps["formatDisplayValue"] | formatSingleDateValue | — |
getDefaultMonth | DatepickerBaseSingleProps["getDefaultMonth"] | getSingleDefaultMonth | — |
getShouldCloseOnSelect | DatepickerBaseSingleProps["getShouldCloseOnSelect"] | getShouldCloseSingleOnSelect | — |
hasPortal | boolean | — | 달력을 `body` 로 내보내 **잘리는 조상을 탈출한다.** 기본 배치는 제자리(`absolute`)라 조상에 `overflow: hidden` 이 있으면 달력이 잘려 날짜를 고를 수 없다. 카드·팝업 안에 넣을 때 켠다. `Tooltip`·`Select` 의 같은 이름 prop 과 한 규칙이다. |
id | string | — | — |
infoMessage | string | — | — |
inputRef | Ref<HTMLInputElement> | — | — |
isClearable | boolean | — | — |
isTextInputBlocked | boolean | — | — |
onClear | () => void | — | — |
onSelectedChange | (selected: TSelected | undefined) => void | — | — |
parseDisplayValue | ((options: { text: string; displayFormat: string; locale: Locale; isDateAllowed: (date: Date) => boolean; isFinal?: boolean | undefined; }) => Date | undefined) | undefined | parseSingleDateValue | 글자 → 값. `formatDisplayValue` 의 역방향이다. **이것을 넘긴 모드만 직접 입력이 열린다** (KRDS 가이드 675쪽 접근성 01 — 선택기가 있어도 입력 필드를 읽기 전용으로 만들지 않는다). 넘기지 않으면 예전처럼 `readonly` 입력이다. |
placeholder | string | — | — |
readOnly | boolean | — | — |
selected | TSelected | undefined | — | — |
shouldCloseOnSelect | boolean | — | — |
unit | string | — | — |
DatepickerProps 에서 자동 생성됨 (components/Datepicker/Datepicker.tsx). 표준 DOM 속성 297개는 그대로 전달되며 표에서 생략했다.
DateRangePicker
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
calendarButtonTitle | string | — | — |
calendarLabel | string | — | 캘린더 팝업의 접근 이름. i18n 을 위해 열어둔다. |
className | string | undefined | — | — |
clearButtonTitle | string | — | 지우기 버튼의 접근 이름. 소비자의 어휘·언어로 바꿀 수 있어야 한다 (a11y.md §9) |
dayPickerProps | DatepickerDayPickerProps<TDayPickerProps> | — | — |
defaultIsCalendarOpen | boolean | false | — |
disabled | boolean | — | — |
displayFormat | string | — | — |
dropdownClassName | string | — | — |
errorMessage | string | — | — |
formatDisplayValue | DateRangePickerBaseProps["formatDisplayValue"] | formatRangeDateValue | — |
getDefaultMonth | DateRangePickerBaseProps["getDefaultMonth"] | getRangeDefaultMonth | — |
getShouldCloseOnSelect | DateRangePickerBaseProps["getShouldCloseOnSelect"] | getShouldCloseRangeOnSelect | — |
hasPortal | boolean | — | 달력을 `body` 로 내보내 **잘리는 조상을 탈출한다.** 기본 배치는 제자리(`absolute`)라 조상에 `overflow: hidden` 이 있으면 달력이 잘려 날짜를 고를 수 없다. 카드·팝업 안에 넣을 때 켠다. `Tooltip`·`Select` 의 같은 이름 prop 과 한 규칙이다. |
id | string | — | — |
infoMessage | string | — | — |
inputRef | Ref<HTMLInputElement> | — | — |
isClearable | boolean | — | — |
isTextInputBlocked | boolean | — | — |
onClear | () => void | — | — |
onSelectedChange | (selected: TSelected | undefined) => void | — | — |
parseDisplayValue | ((options: { text: string; displayFormat: string; locale: Locale; isDateAllowed: (date: Date) => boolean; isFinal?: boolean | undefined; }) => DateRange | undefined) | undefined | parseRangeDateValue | 글자 → 값. `formatDisplayValue` 의 역방향이다. **이것을 넘긴 모드만 직접 입력이 열린다** (KRDS 가이드 675쪽 접근성 01 — 선택기가 있어도 입력 필드를 읽기 전용으로 만들지 않는다). 넘기지 않으면 예전처럼 `readonly` 입력이다. |
placeholder | string | — | — |
readOnly | boolean | — | — |
selected | TSelected | undefined | — | — |
shouldCloseOnSelect | boolean | — | — |
unit | string | — | — |
DateRangePickerProps 에서 자동 생성됨 (components/Datepicker/DateRangePicker.tsx). 표준 DOM 속성 297개는 그대로 전달되며 표에서 생략했다.
DateMultiplePicker
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
calendarButtonTitle | string | — | — |
calendarLabel | string | — | 캘린더 팝업의 접근 이름. i18n 을 위해 열어둔다. |
className | string | undefined | — | — |
clearButtonTitle | string | — | 지우기 버튼의 접근 이름. 소비자의 어휘·언어로 바꿀 수 있어야 한다 (a11y.md §9) |
dayPickerProps | DatepickerDayPickerProps<TDayPickerProps> | — | — |
defaultIsCalendarOpen | boolean | — | — |
disabled | boolean | — | — |
displayFormat | string | — | — |
dropdownClassName | string | — | — |
errorMessage | string | — | — |
formatDisplayValue | DateMultiplePickerBaseProps["formatDisplayValue"] | formatMultipleDateValue | — |
getDefaultMonth | DateMultiplePickerBaseProps["getDefaultMonth"] | getMultipleDefaultMonth | — |
getShouldCloseOnSelect | DateMultiplePickerBaseProps["getShouldCloseOnSelect"] | getShouldCloseMultipleOnSelect | — |
hasPortal | boolean | — | 달력을 `body` 로 내보내 **잘리는 조상을 탈출한다.** 기본 배치는 제자리(`absolute`)라 조상에 `overflow: hidden` 이 있으면 달력이 잘려 날짜를 고를 수 없다. 카드·팝업 안에 넣을 때 켠다. `Tooltip`·`Select` 의 같은 이름 prop 과 한 규칙이다. |
id | string | — | — |
infoMessage | string | — | — |
inputRef | Ref<HTMLInputElement> | — | — |
isClearable | boolean | — | — |
onClear | () => void | — | — |
onSelectedChange | (selected: TSelected | undefined) => void | — | — |
placeholder | string | — | — |
readOnly | boolean | — | — |
selected | TSelected | undefined | — | — |
shouldCloseOnSelect | boolean | — | — |
unit | string | — | — |
DateMultiplePickerProps 에서 자동 생성됨 (components/Datepicker/DateMultiplePicker.tsx). 표준 DOM 속성 297개는 그대로 전달되며 표에서 생략했다.