@mshafiqyajid/react-calendar
Headless calendar hook and styled date-picker calendar for React. Single date, date range, and multiple-date selection modes. Month / year / decade drill-down views. Keyboard navigation (arrow keys, Page Up/Down, Home/End), full ARIA grid semantics, disabled dates, min/max bounds, week numbers, configurable first day of week, and smooth transitions.
Playground #
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
No date selected
Props
TSX
import { CalendarStyled } from "@mshafiqyajid/react-calendar/styled";
import "@mshafiqyajid/react-calendar/styles.css";
<CalendarStyled />Install #
npm install @mshafiqyajid/react-calendar Quick start #
import { CalendarStyled } from "@mshafiqyajid/react-calendar/styled";
import "@mshafiqyajid/react-calendar/styles.css";
import { useState } from "react";
export function Example() {
const [value, setValue] = useState<Date | null>(null);
return (
<CalendarStyled
value={value}
onChange={setValue}
/>
);
} Range selection #
Pass mode="range" and a [Date | null, Date | null] value to select a date range. Hover shows a live range preview.
import { CalendarStyled } from "@mshafiqyajid/react-calendar/styled";
import "@mshafiqyajid/react-calendar/styles.css";
import { useState } from "react";
export function RangeExample() {
const [range, setRange] = useState<[Date | null, Date | null]>([null, null]);
return (
<CalendarStyled
mode="range"
value={range}
onChange={setRange}
/>
);
} Multiple selection #
<CalendarStyled
mode="multiple"
value={dates}
onChange={setDates}
/> Disabled dates #
// Disable specific dates
<CalendarStyled
disabledDates={[new Date(2024, 11, 25), new Date(2024, 0, 1)]}
/>
// Disable with a predicate — e.g. weekends
<CalendarStyled
disabledDates={(date) => date.getDay() === 0 || date.getDay() === 6}
/>
// Min/max bounds
<CalendarStyled
minDate={new Date()}
maxDate={new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)}
/> Headless usage #
import { useCalendar } from "@mshafiqyajid/react-calendar";
const {
value,
setValue,
viewMonth,
setViewMonth,
view,
setView,
weeks,
weekDayLabels,
monthProps,
navProps,
getDateProps,
hoverDate,
setHoverDate,
yearMonths,
decadeYears,
goToPrev,
goToNext,
goToToday,
} = useCalendar({
mode: "single",
firstDayOfWeek: 1,
});
// Render your own calendar UI using the returned props Keyboard #
| Key | Action |
|---|---|
| Arrow keys | Navigate day by day (up/down = 1 week) |
| Enter / Space | Select focused date |
| PageDown / PageUp | Next / previous month |
| Shift+PageDown / Shift+PageUp | Next / previous year |
| Home | Start of current week |
| End | End of current week |
| Ctrl+Home | First day of current month |
| Ctrl+End | Last day of current month |
Props #
| Prop | Type | Default | Description |
|---|---|---|---|
| value | Date | null | [Date|null, Date|null] | Date[] | — | Controlled selection value |
| defaultValue | same | — | Uncontrolled initial value |
| onChange | (value) => void | — | Called on selection change |
| mode | "single" | "range" | "multiple" | "single" | Selection mode |
| view | "month" | "year" | "decade" | — | Controlled drill-down view |
| defaultView | same | "month" | Uncontrolled initial view |
| month | Date | — | Controlled viewport month |
| defaultMonth | Date | current month | Uncontrolled initial month |
| onMonthChange | (month: Date) => void | — | Called when viewport month changes |
| minDate | Date | — | Earliest selectable date |
| maxDate | Date | — | Latest selectable date |
| disabledDates | Date[] | ((date: Date) => boolean) | — | Disabled date list or predicate |
| firstDayOfWeek | 0 | 1 | 6 | 0 | First day of week (0=Sun, 1=Mon, 6=Sat) |
| locale | string | browser locale | Locale for day/month labels |
| showOutsideDays | boolean | true | Show days from adjacent months |
| showWeekNumbers | boolean | false | Show ISO week number column |
| fixedWeeks | boolean | false | Always render 6 rows |
| disabledDays | number[] | — | Disable specific days of the week (0=Sun … 6=Sat). E.g. [0, 6] disables weekends |
| markedDates | { date: Date; color?: string }[] | — | Highlight arbitrary dates with a colored dot. Defaults to accent color when color is omitted |
| showTodayButton | boolean | false | Render a "Today" button in the header that navigates back to the current month |
| numberOfMonths | number | 1 | Show multiple months side by side. Prev/next navigate by this many months at once |
| renderDay | (date, isCurrentMonth, isToday, isSelected) => ReactNode | — | Custom renderer for button children. Replaces the default day number; the button itself (with ARIA/data attrs) is still rendered |
| size | "sm" | "md" | "lg" | "md" | Calendar size |
| className | string | — | Extra CSS class on root element |
| style | CSSProperties | — | Inline style on root element |
| ref | Ref<HTMLDivElement> | — | Forwarded to root div |
Data attributes #
| Attribute | Where | Description |
|---|---|---|
| data-mode | Root | "single" | "range" | "multiple" |
| data-view | Root | "month" | "year" | "decade" |
| data-size | Root | "sm" | "md" | "lg" |
| data-selected | Day cell | Present when date is selected |
| data-today | Day cell | Present on today's date |
| data-disabled | Day cell | Present when date is disabled |
| data-outside-month | Day cell | Present for days outside the current month |
| data-in-range | Day cell | Present for days within a selected range |
| data-range-start | Day cell | Present on the range start date |
| data-range-end | Day cell | Present on the range end date |
CSS variables #
:root {
--rcal-bg: #ffffff;
--rcal-border: #e4e4e7;
--rcal-radius: 12px;
--rcal-fg: #18181b;
--rcal-fg-muted: #71717a;
--rcal-fg-outside: #a1a1aa;
--rcal-day-size: 2.25rem;
--rcal-day-radius: 50%;
--rcal-selected-bg: #18181b;
--rcal-selected-fg: #ffffff;
--rcal-today-ring: #18181b;
--rcal-range-bg: #f4f4f5;
--rcal-duration-slide: 250ms;
--rcal-duration-fade: 150ms;
}