A Tailwind-first React date picker and date range picker built on react-aria-components. Zero config, accessible, customizable.
- No extra CSS required — works out of the box with Tailwind CSS
- Built on react-aria — full accessibility (ARIA patterns, keyboard nav, screen readers)
- No design system lock-in — no Radix, no shadcn, no Material UI dependency
- classNames prop — override any visual part without forking
- Optional slots — swap in your own Button/Select/Input/Icon components
- Tree-shakeable — only ship what you use
npm install @sandylib27/react-aria-datepicker react-aria-components @internationalized/dateimport { DatePicker } from '@sandylib27/react-aria-datepicker'
import { CalendarDate } from '@internationalized/date'
import { useState } from 'react'
function App() {
const [date, setDate] = useState<CalendarDate | null>(null)
return <DatePicker value={date} onChange={setDate} />
}That's it. If you have Tailwind CSS configured, it looks good immediately.
| Prop | Type | Default | Description |
|---|---|---|---|
value |
CalendarDate | null |
— | Selected date |
onChange |
(date: CalendarDate | null) => void |
— | Called when date changes |
dateFormat |
string |
"dd-mm-yyyy" |
Format pattern for text input |
minValue |
CalendarDate |
— | Minimum selectable date |
maxValue |
CalendarDate |
— | Maximum selectable date |
placement |
Placement |
"bottom start" |
Popover position |
showClear |
boolean |
false |
Show clear button in calendar |
hideMonthDropdown |
boolean |
false |
Show plain text instead of dropdown |
hideYearDropdown |
boolean |
false |
Show plain text instead of dropdown |
isDisabled |
boolean |
false |
Disable the picker |
hasError |
boolean |
false |
Show error border state |
isEdited |
boolean |
false |
Show edited border state |
readOnly |
boolean |
false |
Make input read-only |
classNames |
DatePickerClassNames |
— | Override classes for any part |
slots |
DatePickerSlots |
— | Swap sub-components |
className |
string |
— | Root container class |
Same as DatePicker plus:
| Prop | Type | Default | Description |
|---|---|---|---|
value |
DateRangeValue | null |
— | { start, end } |
onChange |
(range: DateRangeValue | null) => void |
— | Called on confirm |
errorMessage |
string |
— | Error text below inputs |
renderTrigger |
(props) => ReactNode |
— | Custom trigger render |
Override any part of the component:
<DatePicker
value={date}
onChange={setDate}
classNames={{
calendar: "bg-white dark:bg-zinc-900 shadow-xl rounded-xl border-0",
daySelected: "bg-indigo-600 text-white",
dayHover: "hover:bg-indigo-100 dark:hover:bg-indigo-900",
input: "border-gray-300 rounded-md",
header: "border-b-0 pb-2",
navButton: "text-indigo-600 hover:bg-indigo-50",
}}
/>root, input, inputWrapper, inputIcon, calendar, header, navButton, monthSelect, yearSelect, selectContainer, heading, grid, weekday, day, daySelected, dayHover, dayOutside, dayDisabled, dayToday, dayRangeMiddle, dayRangeStart, dayRangeEnd, footer, clearButton, confirmButton, cancelButton
For projects using shadcn/ui or other design systems, swap internal primitives:
import { DatePicker } from '@sandylib27/react-aria-datepicker'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
function MyDatePicker() {
return (
<DatePicker
value={date}
onChange={setDate}
slots={{
Button: ({ children, className, onClick }) => (
<Button variant="outline" className={className} onClick={onClick}>
{children}
</Button>
),
Input: Input,
Icon: ({ name, size, className }) => (
<MyIcon name={name} size={size} className={className} />
),
}}
/>
)
}interface DatePickerSlots {
Button?: React.ComponentType<{ onClick?; className?; children; disabled? }>
Select?: React.ComponentType<{ value; onValueChange; options; className? }>
Input?: React.ComponentType<React.InputHTMLAttributes<HTMLInputElement>>
Icon?: React.ComponentType<{ name; size?; strokeWidth?; className? }>
}If you're not using Tailwind, import the optional CSS file for baseline styles:
import '@sandylib27/react-aria-datepicker/css'
import { DatePicker } from '@sandylib27/react-aria-datepicker'Override with CSS variables:
[data-datepicker],
[data-datepicker-calendar] {
--dp-primary: #8b5cf6;
--dp-bg: #1e1b4b;
}import { DateRangePicker } from '@sandylib27/react-aria-datepicker'
import type { DateRangeValue } from '@sandylib27/react-aria-datepicker'
function App() {
const [range, setRange] = useState<DateRangeValue | null>(null)
return <DateRangePicker value={range} onChange={setRange} />
}Features:
- Dual-month calendar view
- Draft/confirm pattern (changes only apply on "Confirm")
- Built-in start-before-end validation
- Custom trigger via
renderTriggerprop
- Install:
npm install @sandylib27/react-aria-datepicker react-aria-components @internationalized/date - Uninstall:
npm uninstall react-day-picker - Delete any
datePickerBase.cssor react-day-picker CSS imports - Replace your component:
// Before (react-day-picker)
import { DayPicker } from 'react-day-picker'
const [date, setDate] = useState<Date | null>(null)
// After (react-aria-datepicker)
import { DatePicker, CalendarDate, today, getLocalTimeZone } from '@sandylib27/react-aria-datepicker'
const [date, setDate] = useState<CalendarDate | null>(null)Key difference: this library uses CalendarDate (from @internationalized/date) instead of native Date. This gives you timezone-safe, immutable date values.
- react-aria-components — Calendar grid, accessibility
- @internationalized/date — Immutable CalendarDate type
- Tailwind CSS — Default styling (optional)
- clsx + tailwind-merge — Class merging
MIT