본문으로 건너뛰기

RangePicker

시작과 끝, 두 날짜를 선택합니다. 선택 사항으로 프리셋 제공.

RangePicker 데모: 시작일과 종료일 선택, hover 미리보기
화면의 스타일은 데모용입니다. Kalyx는 CSS를 전혀 포함하지 않습니다.
import { RangePicker } from '@kalyx/react';

구조​

<RangePicker> {/* Root: holds the { start, end } range */}
<RangePicker.Input part="start" /> {/* start-date combobox input */}
<RangePicker.Input part="end" /> {/* end-date combobox input */}
<RangePicker.Popover> {/* Floating-UI portal, role="dialog" */}
<RangePicker.Presets> {/* role="group" of quick-range buttons */}
<RangePicker.Preset value="today">Today</RangePicker.Preset> {/* one quick-range toggle */}
</RangePicker.Presets>
<RangePicker.Calendar /> {/* range-aware month grid */}
</RangePicker.Popover>
</RangePicker>

기본 사용​

import { useState } from 'react';
import { RangePicker, type DateRange } from '@kalyx/react';

function Example() {
const [range, setRange] = useState<DateRange>({ start: null, end: null });
return (
<RangePicker value={range} onChange={setRange}>
<RangePicker.Input part="start" placeholder="시작" />
<RangePicker.Input part="end" placeholder="종료" />
<RangePicker.Popover>
<RangePicker.Calendar />
</RangePicker.Popover>
</RangePicker>
);
}

선택 흐름: 첫 클릭이 start를, 두 번째가 end를 설정합니다. 두 번째 클릭이 더 이르면 자동으로 위치가 교환됩니다.

직접 사용해보기​

라이브 에디터는 React 와 모든 Kalyx 컴포넌트가 이미 스코프에 있는 상태로 실행되므로 import 문을 생략했습니다. 실제 프로젝트로 옮길 때는 import 를 채워 넣으세요. 전체 import 는 위의 일반 코드 블록에 있습니다.

라이브 에디터
function BasicRange() {
  const [range, setRange] = React.useState({ start: null, end: null });
  return (
    <RangePicker value={range} onChange={setRange}>
      <div className="kx-live-row">
        <RangePicker.Input part="start" className="kx-live-input" placeholder="시작" />
        <span aria-hidden>→</span>
        <RangePicker.Input part="end" className="kx-live-input" placeholder="종료" />
      </div>
      <RangePicker.Popover className="kx-live-popover">
        <RangePicker.Calendar
          classNames={{
            header: 'kx-live-header',
            title: 'kx-live-title',
            navButton: 'kx-live-nav',
            grid: 'kx-live-grid',
            gridCell: 'kx-live-cell',
            weekdayHeader: 'kx-live-weekday',
            day: 'kx-live-day-range',
            dayRangeStart: 'kx-live-range-start',
            dayRangeEnd: 'kx-live-range-end',
            dayInRange: 'kx-live-inrange',
            dayToday: 'live-day-today',
            dayOutsideMonth: 'kx-live-outside',
            dayDisabled: 'kx-live-disabled',
          }}
        />
      </RangePicker.Popover>
      <div className="kx-live-value">
        <code>{range.start?.slice(0, 10) ?? 'null'}</code> →
        <code>{range.end?.slice(0, 10) ?? 'null'}</code>
      </div>
    </RangePicker>
  );
}
결과
Loading...

<RangePicker> (Root)​

Prop타입기본값설명
valueDateRange-제어형 범위.
defaultValueDateRange-비제어형 초기 범위.
onChange(range: DateRange) => void-모든 변경(부분 포함)에 호출.
disabledDisabledRule[] | booleanfalse비활성 규칙 또는 전체 비활성.
readOnlybooleanfalse변경 방지.
weekStartsOn0 | 1locale 에서 추론주 시작. 생략하면 locale 에서 추론하고, 명시한 prop 이 우선한다.
displayFormatstring'yyyy-MM-dd'포맷 문자열.
localestring'en-US'BCP 47 로케일.
dir'ltr' | 'rtl''ltr'레이아웃 방향. 'rtl'이면 캘린더 그리드에 dir="rtl"이 붙고 ArrowLeft/ArrowRight가 반전된다(WAI-ARIA grid 패턴). 국제화 참고.
displayTimezonestring-IANA 타임존. 설정하면 Input 포맷·Calendar 하이라이트·onChange 값이 모두 이 타임존의 civil day 기준이 된다. Timezone 참고.
labelsPartial<RangePickerLabels>-ARIA 라벨 재정의(기본값은 영어).
adapterDateAdapterDateFnsAdapter커스텀 어댑터.
childrenReactNode-서브 컴포넌트.

DateRange​

type DateRange = {
start: ISODateString | null;
end: ISODateString | null;
};

선택 중에는 end >= start를 강제하지 않습니다. 커밋 시점에 자동 스왑. onChange에서 { start, end } 형태를 항상 받습니다.

<RangePicker.Input>​

입력 두 개(start용 하나, end용 하나)가 필요합니다. 먼저 렌더된 입력이 Floating UI 기준점이 됩니다.

Prop타입설명
part'start' | 'end'필수. 이 입력이 관리하는 범위 측.
formatstringRoot의 displayFormat 오버라이드.

<RangePicker.Popover>​

DatePicker.Popover와 동일한 동작. role="dialog", 바깥 클릭 해제, Escape 지원.

<RangePicker.Calendar>​

범위 강조가 적용된 월 그리드.

Prop타입설명
classNamesRangePickerCalendarClassNames스타일.
selectionMode'range' | 'week' (기본 'range')'week' 이면 클릭 한 번에 주 전체를 선택한다. WeekPicker.Calendar 가 바로 이 컴포넌트에 selectionMode 를 'week' 으로 고정한 것이다.
weekAnchor'calendar' | 'clicked' (기본 'calendar')selectionMode="week" 일 때만 유효하다. 'calendar' 는 weekStartsOn 경계로 스냅하고, 'clicked' 는 클릭한 날에 고정된 7일 구간을 쓴다.
fixedWeeksboolean (기본 false)항상 6주 행을 렌더. 지정하지 않으면 4~6행이라 달마다 popover 높이가 바뀐다.
showWeekNumberboolean (기본 false)그리드 왼쪽에 ISO 8601 주차 열(1~53)을 렌더한다. 이 열은 WAI-ARIA grid 데이터 영역 밖의 <th scope="row"> 라 날짜 셀 키보드 내비게이션에 영향을 주지 않는다. classNames 의 weekNumberHeader / weekNumber 키로 스타일링한다.
type RangePickerCalendarClassNames = {
root?: string;
header?: string;
title?: string;
navButton?: string;
grid?: string;
gridRow?: string;
gridCell?: string;
day?: string;
dayRangeStart?: string; // 범위의 시작 날짜
dayRangeEnd?: string; // 범위의 끝 날짜
dayInRange?: string; // 시작과 끝 사이 날짜
dayToday?: string;
dayDisabled?: string;
dayOutsideMonth?: string;
weekdayHeader?: string;
weekNumberHeader?: string; // showWeekNumber 를 켰을 때만 렌더된다
weekNumber?: string; // showWeekNumber 를 켰을 때만 렌더된다
};

data-* 어트리뷰트​

날짜 버튼은 범위를 인식하는 상태 어트리뷰트를 방출합니다(활성일 때만 존재). 스타일링 참고.

어트리뷰트활성 조건
data-range-start날짜가 범위의 시작일.
data-range-end날짜가 범위의 종료일.
data-in-range날짜가 시작과 종료 사이(배타적)에 있음.
data-today날짜가 오늘.
data-focused날짜가 키보드 포커스를 가짐.
data-outside-month날짜가 인접한 월에 속함.

<RangePicker.Presets>​

빠른 선택 버튼 컨테이너.

Prop타입설명
classNamesRangePickerPresetsClassNames스타일.
childrenReactNode<RangePicker.Preset> 자식.
type RangePickerPresetsClassNames = {
root?: string;
preset?: string;
presetActive?: string;
};

<RangePicker.Preset>​

단일 프리셋 버튼. 내장 value 키 또는 커스텀 range를 제공하세요.

Prop타입설명
valuePresetKey내장 프리셋 키.
rangeDateRange커스텀 범위 (value 대신).
childrenReactNode버튼 라벨.

내장 PresetKey​

type PresetKey =
| 'today'
| 'yesterday'
| 'last7days'
| 'last30days'
| 'thisWeek'
| 'lastWeek'
| 'thisMonth'
| 'lastMonth'
| 'thisYear';

패턴​

사이드바 프리셋 + 캘린더​

<RangePicker value={range} onChange={setRange}>
<div className="flex gap-2">
<RangePicker.Input part="start" />
<RangePicker.Input part="end" />
</div>
<RangePicker.Popover className="flex gap-4 p-3">
<RangePicker.Presets className="flex flex-col gap-1">
<RangePicker.Preset value="today">오늘</RangePicker.Preset>
<RangePicker.Preset value="last7days">지난 7일</RangePicker.Preset>
<RangePicker.Preset value="last30days">지난 30일</RangePicker.Preset>
<RangePicker.Preset value="thisMonth">이번 달</RangePicker.Preset>
</RangePicker.Presets>
<RangePicker.Calendar />
</RangePicker.Popover>
</RangePicker>
라이브 에디터
function RangeWithPresets() {
  const [range, setRange] = React.useState({ start: null, end: null });
  return (
    <RangePicker value={range} onChange={setRange}>
      <div className="kx-live-row">
        <RangePicker.Input part="start" className="kx-live-input" placeholder="시작" />
        <RangePicker.Input part="end" className="kx-live-input" placeholder="종료" />
      </div>
      <RangePicker.Popover className="kx-live-popover kx-live-popover--split">
        <RangePicker.Presets
          classNames={{
            root: 'kx-live-presets',
            preset: 'kx-live-preset',
            presetActive: 'kx-live-preset-active',
          }}
        >
          <RangePicker.Preset value="today">오늘</RangePicker.Preset>
          <RangePicker.Preset value="yesterday">어제</RangePicker.Preset>
          <RangePicker.Preset value="last7days">지난 7일</RangePicker.Preset>
          <RangePicker.Preset value="last30days">지난 30일</RangePicker.Preset>
          <RangePicker.Preset value="thisMonth">이번 달</RangePicker.Preset>
          <RangePicker.Preset value="lastMonth">지난달</RangePicker.Preset>
        </RangePicker.Presets>
        <RangePicker.Calendar
          classNames={{
            header: 'kx-live-header',
            title: 'kx-live-title',
            navButton: 'kx-live-nav',
            grid: 'kx-live-grid',
            gridCell: 'kx-live-cell',
            weekdayHeader: 'kx-live-weekday',
            day: 'kx-live-day-range',
            dayRangeStart: 'kx-live-range-start',
            dayRangeEnd: 'kx-live-range-end',
            dayInRange: 'kx-live-inrange',
            dayToday: 'live-day-today',
            dayOutsideMonth: 'kx-live-outside',
            dayDisabled: 'kx-live-disabled',
          }}
        />
      </RangePicker.Popover>
    </RangePicker>
  );
}
결과
Loading...

커스텀 프리셋​

<RangePicker.Preset
range={{
start: '2026-01-01T00:00:00.000Z',
end: '2026-06-30T00:00:00.000Z',
}}>
2026 상반기
</RangePicker.Preset>

범위 제약​

<RangePicker
value={range}
onChange={setRange}
disabled={[{ before: '2026-01-01T00:00:00.000Z' }]}>
...
</RangePicker>

네이티브 폼 제출​

각 입력에 name을 지정하면 화면에는 포맷된 날짜를 표시하되, hidden input이 각 시작·종료 값을 UTC-ISO 문자열로 제출합니다.

<RangePicker defaultValue={range}>
<RangePicker.Input part="start" name="startDate" />
<RangePicker.Input part="end" name="endDate" />
</RangePicker>

관련​