A responsive and accessible date range picker component built with React
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Low-level calendar rendering components for building custom date picker interfaces. These components handle the visual presentation and interaction of calendar grids, months, and individual days.
Complete calendar component with navigation controls, keyboard shortcuts, and accessibility features.
/**
* Complete calendar component with navigation and accessibility
* @param props - DayPicker configuration
* @returns Full calendar component with navigation
*/
function DayPicker(props: DayPickerProps): ReactElement;
interface DayPickerProps {
// Calendar presentation
enableOutsideDays?: boolean; // Default: false
numberOfMonths?: number; // Default: 2
orientation?: 'horizontal' | 'vertical' | 'verticalScrollable'; // Default: 'horizontal'
withPortal?: boolean; // Default: false
onOutsideClick?: () => void; // Default: () => {}
hidden?: boolean; // Default: false
initialVisibleMonth?: () => moment.Moment; // Default: () => moment()
firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | null; // Default: null (uses moment locale)
renderCalendarInfo?: () => ReactNode; // Default: null
calendarInfoPosition?: 'top' | 'bottom' | 'before' | 'after'; // Default: 'bottom'
hideKeyboardShortcutsPanel?: boolean; // Default: false
daySize?: number; // Default: 39 (non-negative integer)
isRTL?: boolean; // Default: false
verticalHeight?: number; // Default: null (non-negative integer)
noBorder?: boolean; // Default: false
transitionDuration?: number; // Default: undefined (non-negative integer)
verticalBorderSpacing?: number; // Default: undefined (non-negative integer)
horizontalMonthPadding?: number; // Default: 13 (non-negative integer)
// Navigation
dayPickerNavigationInlineStyles?: object; // Default: null
disablePrev?: boolean; // Default: false
disableNext?: boolean; // Default: false
navPosition?: 'navPositionTop' | 'navPositionBottom'; // Default: 'navPositionTop'
navPrev?: ReactNode; // Default: null
navNext?: ReactNode; // Default: null
renderNavPrevButton?: (props: any) => ReactNode; // Default: null
renderNavNextButton?: (props: any) => ReactNode; // Default: null
noNavButtons?: boolean; // Default: false
noNavNextButton?: boolean; // Default: false
noNavPrevButton?: boolean; // Default: false
onPrevMonthClick?: (newMonth: moment.Moment) => void; // Default: () => {}
onNextMonthClick?: (newMonth: moment.Moment) => void; // Default: () => {}
onMonthChange?: (newMonth: moment.Moment) => void; // Default: () => {}
onYearChange?: (newMonth: moment.Moment) => void; // Default: () => {}
onGetNextScrollableMonths?: (e: Event) => void; // Default: () => {} (VERTICAL_SCROLLABLE only)
onGetPrevScrollableMonths?: (e: Event) => void; // Default: () => {} (VERTICAL_SCROLLABLE only)
// Month customization (mutually exclusive)
renderMonthText?: (month: moment.Moment) => ReactNode; // Default: null (mutually exclusive with renderMonthElement)
renderMonthElement?: (props: any) => ReactNode; // Default: null (mutually exclusive with renderMonthText)
renderWeekHeaderElement?: (day: string) => ReactNode; // Default: null
// Day customization
modifiers?: { [date: string]: Set<string> }; // Default: {} (objectOf ModifiersShape)
renderCalendarDay?: (props: any) => ReactNode; // Default: undefined
renderDayContents?: (day: moment.Moment, modifiers: Set<string>) => ReactNode; // Default: null
onDayClick?: (day: moment.Moment, e: SyntheticEvent) => void; // Default: () => {}
onDayMouseEnter?: (day: moment.Moment, e: SyntheticEvent) => void; // Default: () => {}
onDayMouseLeave?: (day: moment.Moment, e: SyntheticEvent) => void; // Default: () => {}
// Accessibility
isFocused?: boolean; // Default: false
getFirstFocusableDay?: (month: moment.Moment) => moment.Moment; // Default: null
onBlur?: (e: SyntheticEvent) => void; // Default: () => {}
showKeyboardShortcuts?: boolean; // Default: false
onTab?: (e: SyntheticEvent) => void; // Default: () => {}
onShiftTab?: () => void; // Default: () => {}
renderKeyboardShortcutsButton?: (props: any) => ReactNode; // Default: undefined
renderKeyboardShortcutsPanel?: (props: any) => ReactNode; // Default: undefined
// Internationalization
monthFormat?: string; // Default: 'MMMM YYYY'
weekDayFormat?: string; // Default: 'dd'
phrases?: DayPickerPhrasesShape; // Default: DayPickerPhrases
dayAriaLabelFormat?: string; // Default: undefined
}Usage Examples:
import React, { useState } from "react";
import { DayPicker } from "react-dates";
import moment from "moment";
// Basic calendar display
function BasicCalendar() {
const [currentMonth, setCurrentMonth] = useState(moment());
return (
<DayPicker
month={currentMonth}
onPrevMonthClick={setCurrentMonth}
onNextMonthClick={setCurrentMonth}
numberOfMonths={1}
onDayClick={(day) => console.log('Day clicked:', day.format('YYYY-MM-DD'))}
/>
);
}
// Custom calendar with events
function EventCalendar({ events }) {
const [currentMonth, setCurrentMonth] = useState(moment());
const renderDayContents = (day, modifiers) => {
const dayKey = day.format('YYYY-MM-DD');
const dayEvents = events[dayKey] || [];
return (
<div className="calendar-day">
<span className="day-number">{day.format('D')}</span>
{dayEvents.length > 0 && (
<div className="event-dots">
{dayEvents.slice(0, 3).map((event, index) => (
<div
key={index}
className="event-dot"
style={{ backgroundColor: event.color }}
/>
))}
{dayEvents.length > 3 && <span className="more-events">+{dayEvents.length - 3}</span>}
</div>
)}
</div>
);
};
const handleDayClick = (day) => {
const dayEvents = events[day.format('YYYY-MM-DD')] || [];
if (dayEvents.length > 0) {
console.log(`Events for ${day.format('MMM D')}:`, dayEvents);
}
};
return (
<DayPicker
month={currentMonth}
onPrevMonthClick={setCurrentMonth}
onNextMonthClick={setCurrentMonth}
numberOfMonths={1}
renderDayContents={renderDayContents}
onDayClick={handleDayClick}
renderCalendarInfo={() => (
<div className="calendar-legend">
<h4>Event Calendar</h4>
<p>Click on dates with dots to see events</p>
</div>
)}
/>
);
}Grid layout component for displaying multiple calendar months with transitions and animations.
/**
* Grid layout for multiple calendar months
* @param props - CalendarMonthGrid configuration
* @returns Month grid component with transitions
*/
function CalendarMonthGrid(props: CalendarMonthGridProps): ReactElement;
interface CalendarMonthGridProps {
// Layout and presentation
enableOutsideDays?: boolean; // Default: false - Show days from adjacent months
firstVisibleMonthIndex?: number; // Default: 0 - Index of first visible month in grid
horizontalMonthPadding?: number; // Default: 13 (non-negative integer) - Horizontal padding for months
numberOfMonths?: number; // Default: 1 - Number of months to display
orientation?: 'horizontal' | 'vertical' | 'verticalScrollable'; // Default: 'horizontal' - Layout orientation
daySize?: number; // Default: 39 (non-negative integer) - Size of each day cell
isRTL?: boolean; // Default: false - Right-to-left layout
verticalBorderSpacing?: number; // Default: undefined (non-negative integer) - Vertical spacing between day rows
// Animation and transitions
isAnimating?: boolean; // Default: false - Whether grid is currently animating
translationValue?: number; // Default: null - Translation offset for animations
transitionDuration?: number; // Default: 200 (non-negative integer) - Animation duration in milliseconds
onMonthTransitionEnd?: () => void; // Default: () => {} - Called when month transition completes
// Month state and navigation
initialMonth?: moment.Moment; // Default: moment() - Initial month to display
onMonthChange?: (newMonth: moment.Moment) => void; // Default: () => {} - Called when month changes
onYearChange?: (newMonth: moment.Moment) => void; // Default: () => {} - Called when year changes
// Day interaction
onDayClick?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day click handler
onDayMouseEnter?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day mouse enter handler
onDayMouseLeave?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day mouse leave handler
// Day modifiers and styling
modifiers?: { [monthString: string]: { [dateString: string]: Set<string> } }; // Default: {} - Object mapping month strings to date modifiers
// Custom rendering (mutually exclusive)
renderMonthText?: (month: moment.Moment) => ReactNode; // Default: null - Custom month header text renderer (mutually exclusive with renderMonthElement)
renderMonthElement?: (props: { // Default: null - Custom month header element renderer (mutually exclusive with renderMonthText)
month: moment.Moment;
onMonthSelect: (currentMonth: moment.Moment, newMonthVal: string | number) => void;
onYearSelect: (currentMonth: moment.Moment, newYearVal: string | number) => void;
isVisible: boolean;
}) => ReactNode;
// Day rendering customization
renderCalendarDay?: (props: CalendarDayProps) => ReactNode; // Default: undefined - Custom day cell renderer
renderDayContents?: (day: moment.Moment, modifiers: Set<string>) => ReactNode; // Default: null - Custom day contents renderer
// Focus management
focusedDate?: moment.Moment; // Default: null - Currently focused date for keyboard navigation
isFocused?: boolean; // Default: false - Whether to move focus to focusable day
firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | null; // Default: null (uses moment locale) - First day of week (0=Sunday, 6=Saturday)
// Layout callbacks
setMonthTitleHeight?: (height: number) => void; // Default: null - Callback to report month title height
// Internationalization
monthFormat?: string; // Default: 'MMMM YYYY' - Moment.js format for month header
dayAriaLabelFormat?: string; // Default: undefined - Moment.js format for day ARIA labels
phrases?: { // Default: CalendarDayPhrases - Accessibility and i18n text
chooseAvailableDate?: (args: { date: string }) => string;
dateIsUnavailable?: (args: { date: string }) => string;
dateIsSelected?: (args: { date: string }) => string;
dateIsSelectedAsStartDate?: (args: { date: string }) => string;
dateIsSelectedAsEndDate?: (args: { date: string }) => string;
};
}Public Methods:
// Navigation and animation methods (accessible via ref)
onTransitionEnd(): void;
onMonthSelect(currentMonth: moment.Moment, newMonthVal: string | number): void;
onYearSelect(currentMonth: moment.Moment, newYearVal: string | number): void;Usage Examples:
import React, { useState } from "react";
import { CalendarMonthGrid } from "react-dates";
import moment from "moment";
// Multi-month calendar display
function MultiMonthCalendar() {
const [isAnimating, setIsAnimating] = useState(false);
const [currentMonth, setCurrentMonth] = useState(moment());
const handleMonthSelect = (currentMonth, newMonthVal) => {
setIsAnimating(true);
const newMonth = currentMonth.clone().month(parseInt(newMonthVal));
setCurrentMonth(newMonth);
};
const handleTransitionEnd = () => {
setIsAnimating(false);
};
return (
<CalendarMonthGrid
numberOfMonths={3}
orientation="horizontal"
initialMonth={currentMonth}
isAnimating={isAnimating}
onMonthSelect={handleMonthSelect}
onMonthTransitionEnd={handleTransitionEnd}
onDayClick={(day) => console.log('Selected:', day.format('YYYY-MM-DD'))}
/>
);
}
// Responsive calendar grid
function ResponsiveCalendarGrid() {
const [numberOfMonths, setNumberOfMonths] = useState(
window.innerWidth > 768 ? 2 : 1
);
const [orientation, setOrientation] = useState(
window.innerWidth > 768 ? 'horizontal' : 'vertical'
);
React.useEffect(() => {
const handleResize = () => {
const isMobile = window.innerWidth <= 768;
setNumberOfMonths(isMobile ? 1 : 2);
setOrientation(isMobile ? 'vertical' : 'horizontal');
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<CalendarMonthGrid
numberOfMonths={numberOfMonths}
orientation={orientation}
transitionDuration={200}
onDayClick={(day) => console.log('Date selected:', day.format('YYYY-MM-DD'))}
/>
);
}Individual month view component containing the calendar day grid for a single month.
/**
* Individual month view with calendar day grid
* @param props - CalendarMonth configuration
* @returns Single month calendar component
*/
function CalendarMonth(props: CalendarMonthProps): ReactElement;
interface CalendarMonthProps {
// Core month data
month?: moment.Moment; // Default: moment() - The month to display
// Layout and presentation
horizontalMonthPadding?: number; // Default: 13 (non-negative integer) - Horizontal padding for month container
daySize?: number; // Default: 39 (non-negative integer) - Size of each day cell
isVisible?: boolean; // Default: true - Whether month is visible
orientation?: 'horizontal' | 'vertical' | 'verticalScrollable'; // Default: 'horizontal' - Layout orientation
verticalBorderSpacing?: number; // Default: undefined (non-negative integer) - Vertical spacing between day rows
// Day behavior and outside days
enableOutsideDays?: boolean; // Default: false - Show days from adjacent months
firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | null; // Default: null (uses moment locale) - First day of week (0=Sunday, 6=Saturday)
// Day interaction handlers
onDayClick?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day click handler
onDayMouseEnter?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day mouse enter handler
onDayMouseLeave?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day mouse leave handler
// Month/Year selection handlers
onMonthSelect?: (currentMonth: moment.Moment, newMonthVal: string | number) => void; // Default: () => {} - Month selection handler
onYearSelect?: (currentMonth: moment.Moment, newYearVal: string | number) => void; // Default: () => {} - Year selection handler
// Rendering customization (mutually exclusive)
renderMonthText?: (month: moment.Moment) => ReactNode; // Default: null - Custom month header text renderer (mutually exclusive with renderMonthElement)
renderMonthElement?: (props: { // Default: null - Custom month header element renderer (mutually exclusive with renderMonthText)
month: moment.Moment;
onMonthSelect: function;
onYearSelect: function;
isVisible: boolean;
}) => ReactNode;
// Day rendering customization
renderCalendarDay?: (props: CalendarDayProps) => ReactNode; // Default: (props) => <CalendarDay {...props} /> - Custom day cell renderer
renderDayContents?: (day: moment.Moment, modifiers: Set<string>) => ReactNode; // Default: null - Custom day contents renderer
// Day modifiers and styling
modifiers?: { [dateString: string]: Set<string> }; // Default: {} - Object mapping ISO date strings to Sets of modifier strings
// Focus management
focusedDate?: moment.Moment; // Default: null - Currently focused date for keyboard navigation
isFocused?: boolean; // Default: false - Whether to move focus to focusable day
// Layout callback
setMonthTitleHeight?: (height: number) => void; // Default: null - Callback to report month title height
// Internationalization
monthFormat?: string; // Default: 'MMMM YYYY' - Moment.js format for month header
dayAriaLabelFormat?: string; // Default: undefined - Moment.js format for day ARIA labels
phrases?: { // Default: CalendarDayPhrases - Accessibility and i18n text
chooseAvailableDate?: ({ date }: { date: string }) => string;
dateIsUnavailable?: ({ date }: { date: string }) => string;
dateIsSelected?: ({ date }: { date: string }) => string;
dateIsSelectedAsStartDate?: ({ date }: { date: string }) => string;
dateIsSelectedAsEndDate?: ({ date }: { date: string }) => string;
};
}Public Methods:
// Layout and reference methods (accessible via ref)
setMonthTitleHeight(): void;
setCaptionRef(ref: HTMLElement): void;Usage Examples:
import React, { useState, useRef } from "react";
import { CalendarMonth } from "react-dates";
import moment from "moment";
// Single month display
function MonthView({ selectedMonth }) {
const monthRef = useRef(null);
const handleDayClick = (day) => {
console.log('Day selected:', day.format('YYYY-MM-DD'));
};
const renderMonthHeader = (month) => {
return (
<div className="custom-month-header">
<h2>{month.format('MMMM YYYY')}</h2>
<p>Select a date below</p>
</div>
);
};
return (
<CalendarMonth
ref={monthRef}
month={selectedMonth}
onDayClick={handleDayClick}
renderMonthText={renderMonthHeader}
enableOutsideDays={true}
/>
);
}
// Custom styled month
function StyledMonthView({ month, holidays, events }) {
const getModifiers = (day) => {
const modifiers = new Set();
const dayKey = day.format('YYYY-MM-DD');
if (holidays.includes(dayKey)) {
modifiers.add('holiday');
}
if (events[dayKey]) {
modifiers.add('has-events');
}
if (day.day() === 0 || day.day() === 6) {
modifiers.add('weekend');
}
return modifiers;
};
const renderDayContents = (day, modifiers) => {
const hasEvents = modifiers.has('has-events');
const isHoliday = modifiers.has('holiday');
return (
<div className={`day-cell ${isHoliday ? 'holiday' : ''}`}>
<span>{day.format('D')}</span>
{hasEvents && <div className="event-indicator" />}
</div>
);
};
return (
<CalendarMonth
month={month}
renderDayContents={renderDayContents}
modifiers={{ getModifiers }}
onDayClick={(day) => {
const dayEvents = events[day.format('YYYY-MM-DD')];
if (dayEvents) {
console.log('Events for this day:', dayEvents);
}
}}
/>
);
}Individual day cell component in the calendar grid, handling day selection and customization.
/**
* Individual day cell in calendar grid
* @param props - CalendarDay configuration
* @returns Single day cell component
*/
function CalendarDay(props: CalendarDayProps): ReactElement;
interface CalendarDayProps {
// Day data and presentation
day: moment.Moment; // Required: momentPropTypes.momentObj - The moment object representing the day
daySize?: number; // Default: 39 (DAY_SIZE) - nonNegativeInteger - Size of the day cell in pixels
isOutsideDay?: boolean; // Default: false - Whether this day belongs to an adjacent month
// Modifiers and styling
modifiers?: Set<string>; // Default: new Set() - ModifiersShape (Set of strings) - Visual state modifiers
// Focus and interaction state
isFocused?: boolean; // Default: false - Whether the day should receive focus
tabIndex?: 0 | -1; // Default: -1 - Tab navigation index (0 = focusable, -1 = not focusable)
// Event handlers
onDayClick?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Day click handler
onDayMouseEnter?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Mouse enter handler
onDayMouseLeave?: (day: moment.Moment, event: SyntheticEvent) => void; // Default: () => {} - Mouse leave handler
// Custom rendering
renderDayContents?: (day: moment.Moment, modifiers: Set<string>) => ReactNode | null; // Default: null - Custom day contents renderer
// Accessibility
ariaLabelFormat?: string; // Default: 'dddd, LL' - Moment.js format string for ARIA labels
// Internationalization
phrases?: { // Default: CalendarDayPhrases - Accessibility and i18n text
chooseAvailableDate?: ({ date }: { date: string }) => string;
dateIsUnavailable?: ({ date }: { date: string }) => string;
dateIsSelected?: ({ date }: { date: string }) => string;
dateIsSelectedAsStartDate?: ({ date }: { date: string }) => string;
dateIsSelectedAsEndDate?: ({ date }: { date: string }) => string;
};
}Public Methods:
// Event handling methods (accessible via ref)
onDayClick(event: SyntheticEvent): void;
onDayMouseEnter(event: SyntheticEvent): void;
onDayMouseLeave(event: SyntheticEvent): void;
onKeyDown(event: KeyboardEvent): void;Usage Examples:
import React from "react";
import { CalendarDay } from "react-dates";
import moment from "moment";
// Custom day rendering
function CustomCalendarDay({ day, isSelected, isBlocked, events }) {
const modifiers = new Set();
if (isSelected) modifiers.add('selected');
if (isBlocked) modifiers.add('blocked');
if (events?.length > 0) modifiers.add('has-events');
const renderDayContents = (day, modifiers) => {
const dayNumber = day.format('D');
const hasEvents = modifiers.has('has-events');
return (
<div className="custom-day-contents">
<span className="day-number">{dayNumber}</span>
{hasEvents && (
<div className="event-indicators">
{events.slice(0, 2).map((event, index) => (
<div
key={index}
className="event-dot"
style={{ backgroundColor: event.color }}
title={event.title}
/>
))}
{events.length > 2 && (
<span className="more-events">+{events.length - 2}</span>
)}
</div>
)}
</div>
);
};
const handleDayClick = (day, event) => {
if (!isBlocked) {
console.log('Day clicked:', day.format('YYYY-MM-DD'));
if (events?.length > 0) {
console.log('Day events:', events);
}
}
};
return (
<CalendarDay
day={day}
modifiers={modifiers}
onDayClick={handleDayClick}
renderDayContents={renderDayContents}
onDayMouseEnter={(day) => {
if (events?.length > 0) {
// Show event preview on hover
console.log('Hovering day with events:', events);
}
}}
/>
);
}
// Minimal day cell
function MinimalCalendarDay({ day, onClick }) {
const handleClick = (day, event) => {
onClick?.(day, event);
};
return (
<CalendarDay
day={day}
onDayClick={handleClick}
renderDayContents={(day) => (
<span className="simple-day">{day.format('D')}</span>
)}
/>
);
}// Define custom modifiers for day states
const getCustomModifiers = (day, selectedDates, blockedDates, highlightedDates) => {
const modifiers = new Set();
const dayKey = day.format('YYYY-MM-DD');
// Standard modifiers
if (selectedDates.includes(dayKey)) modifiers.add('selected');
if (blockedDates.includes(dayKey)) modifiers.add('blocked');
if (highlightedDates.includes(dayKey)) modifiers.add('highlighted');
// Custom business logic modifiers
if (day.day() === 0 || day.day() === 6) modifiers.add('weekend');
if (day.isSame(moment(), 'day')) modifiers.add('today');
if (day.isBefore(moment(), 'day')) modifiers.add('past');
return modifiers;
};
// Usage in calendar components
<CalendarMonth
month={currentMonth}
modifiers={{ getCustomModifiers }}
renderDayContents={(day, modifiers) => {
let className = 'calendar-day';
if (modifiers.has('weekend')) className += ' weekend';
if (modifiers.has('today')) className += ' today';
if (modifiers.has('blocked')) className += ' blocked';
return <div className={className}>{day.format('D')}</div>;
}}
/>// Advanced month navigation with constraints
function ConstrainedCalendar({ minDate, maxDate }) {
const [currentMonth, setCurrentMonth] = useState(moment());
const handlePrevMonth = (newMonth) => {
if (!minDate || newMonth.isAfter(minDate, 'month')) {
setCurrentMonth(newMonth);
}
};
const handleNextMonth = (newMonth) => {
if (!maxDate || newMonth.isBefore(maxDate, 'month')) {
setCurrentMonth(newMonth);
}
};
return (
<DayPicker
month={currentMonth}
onPrevMonthClick={handlePrevMonth}
onNextMonthClick={handleNextMonth}
renderNavPrevButton={(props) => (
<button
{...props}
disabled={minDate && currentMonth.clone().subtract(1, 'month').isBefore(minDate, 'month')}
>
← Previous
</button>
)}
renderNavNextButton={(props) => (
<button
{...props}
disabled={maxDate && currentMonth.clone().add(1, 'month').isAfter(maxDate, 'month')}
>
Next →
</button>
)}
/>
);
}All calendar components include comprehensive accessibility features:
phrases propInstall with Tessl CLI
npx tessl i tessl/npm-react-dates