@mshafiqyajid/react-date-picker
Headless date picker hook and styled component. Single and range mode, multi-month view, captionLayout dropdowns, renderDay slot, modifiers, inline calendar, full floating-UI placement (flip + shift), live-region month announcements, keyboard navigation that skips disabled cells. Zero dependencies.
Playground #
Props
TSX
import { DatePickerStyled } from "@mshafiqyajid/react-date-picker/styled";
import "@mshafiqyajid/react-date-picker/styles.css";
<DatePickerStyled
value={value}
onChange={setValue}
/>Install #
npm install @mshafiqyajid/react-date-picker Quick start #
import { DatePickerStyled } from "@mshafiqyajid/react-date-picker/styled";
import "@mshafiqyajid/react-date-picker/styles.css";
const [date, setDate] = useState<Date | null>(null);
<DatePickerStyled value={date} onChange={setDate} tone="primary" />
// Range mode
const [range, setRange] = useState<[Date, Date] | null>(null);
<DatePickerStyled value={range} onChange={setRange} mode="range" /> Headless #
import { useDatePicker } from "@mshafiqyajid/react-date-picker";
const {
days,
month,
year,
getDayProps,
prevMonth,
nextMonth,
} = useDatePicker({
mode: "single",
defaultValue: new Date(),
});
return (
<div className="cal">
<header>
<button onClick={prevMonth}>‹</button>
<span>{month + 1} / {year}</span>
<button onClick={nextMonth}>›</button>
</header>
<div className="grid">
{days.map((day) => (
<button key={day.toISOString()} {...getDayProps(day)}>
{day.getDate()}
</button>
))}
</div>
</div>
); API #
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue | Date | [Date, Date] | null | — | Controlled or uncontrolled value |
| onChange | (v) => void | — | Called on selection |
| mode | "single" | "range" | "single" | Single date or date range |
| minDate / maxDate | Date | — | Date bounds |
| disabledDates | Date[] | (date) => boolean | — | Dates to disable |
| placeholder / format | string | "Select date" / "MMM D, YYYY" | Trigger text |
| size / tone | "sm" | "md" | "lg" / "neutral" | "primary" | "md" / "neutral" | Visual |
| disabled / clearable | boolean | false | Existing behavior |
| weekStartsOn / locale | 0–6 / string | 0 / "en-US" | Localization |
0.2.0 additions #
| Prop | Type | Default | Description |
|---|---|---|---|
| numberOfMonths | 1 | 2 | 3 | 1 | Render N month grids side-by-side in the popover |
| pagedBy | 1 | "all" | 1 | Chevron step. "all" advances by numberOfMonths. |
| renderDay | (ctx) => ReactNode | — | Replace day cell content. ctx: { date, isToday, isSelected, isInRange, isDisabled, isOutsideMonth, modifiers } |
| captionLayout | "label" | "dropdown" | "dropdown-months" | "dropdown-years" | "label" | Header style — chevron-only label or native dropdowns |
| fromYear / toYear | number | now-100 / now+10 | Year dropdown bounds |
| inline | boolean | false | Render the calendar always-visible without a trigger |
| open / defaultOpen / onOpenChange | boolean / fn | — | Controlled-open API for the popover |
| month / defaultMonth | { month, year } | — | Controlled / initial visible month |
| onMonthChange / onYearChange | (...) => void | — | Navigation callbacks |
| modifiers | Record<string, Date[] | (date) => boolean> | — | Tag dates; emits data-mod-<name>="true" + data-modifiers="..." per cell |
| showOutsideDays | boolean | true | Render leading/trailing outside-month cells |
| fixedWeeks | boolean | true | Pad to 6 rows even for short months |
| skipDisabledOnArrowKey | boolean | true | Arrow keys skip disabled cells |
| closeOnSelect | boolean | single: true, range: false | Auto-close after selection |
| clearOnReselect | boolean | false | Clicking the selected day again clears it |
| monthChangeAnnouncement | (month, year) => string | "${month} ${year}" | Live-region announcement copy |
| onDayMouseEnter / onDayMouseLeave / onDayFocus | (date, e?) => void | — | Day-level hooks for tooltips, etc. |
| placement | bottom-start | bottom-end | top-start | top-end | "bottom-start" | Popover placement |
| offset / collisionPadding | number | 6 / 8 | Floating-UI distances |
| flip / shift | boolean | true | Auto-flip + shift back into view |
| strategy | "absolute" | "fixed" | "absolute" | CSS positioning strategy |