Range Calendar
A calendar component for displaying & selecting date ranges.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Features
- Full keyboard navigation
- Can be controlled or uncontrolled
- Focus is fully managed
- Localization support
- Highly composable
Overview
The Range Calendar builder enables you to compose a fully accessible calendar for selecting and displaying ranges of dates.
Anatomy
- Calendar: The container of the months and days of the calendar
- Cell: A single date in the calendar
- Grid: A month in the calendar
- Previous Button: A button for navigating to the previous page of the calendar
- Next Button: A button for navigating to the next page of the calendar
- Heading: A visual heading for the calendar
Scaffold a Range Calendar
The following tutorial is designed to help you get started using the Range Calendar builder, and explain some of the concepts to help you understand how it works.
Let's start by initializing a calendar with the createRangeCalendar
builder function, and
destructuring everything we'll need to compose it. We'll cover what each of these properties are in
more detail as we go.
The calendar
element is the root container for everything in the calendar, and all the other
elements will be contained within it.
We can now add the header of our calendar, which will contain the heading
and page navigation
buttons (prevButton
& nextButton
).
Something you may notice is that we aren't using an HTML heading element for the heading
. This is
because the builder automatically adds an accessible heading to the calendar, which is not visible
to sighted users, and will be the first thing the screen reader will announce when the user
navigates to the calendar. The heading here is strictly for visual purposes.
The headingValue
state is a string that contains the month and year of the current page of the
calendar. It reacts to changes in the month
and year
states, and is formatted according to the
locale
prop.
Now that we've taken care of the header, we can add the meat of the calendar, which is the month(s) and their days.
We'll be using the months
store to iterate over each month being displayed in the calendar, which
defaults to 1 but can be changed with the numberOfMonths
prop. The months
store is an array of
Month
objects, which look like this:
Since we're taking the recommended approach of rendering the calendar grid using a table, we'll be
using the weeks
property of each month to render the rows of the table. We'll also use the
weekdays
state to render the column headers of the table, which is an array of formatted day names
according to the locale
prop.
We're setting the <thead />
to aria-hidden="true"
, because the day of the week is already read
by screen readers as they focus each cell in the table, and it isn't useful to have it read twice.
Now we can finish off the calendar by rendering the weeks and days within each week.
And that, along with your own styles and markup additions, is all you need to build a functional, fully accessible calendar component!
I know it may seem like it requires a lot of code to get a calendar up and running, but the functionality that the 43 lines of code above provide is quite powerful, which we'll learn more about in the following sections.
Placeholder
In this section, we'll learn about the placeholder props, which are used to change what the calendar displays when no date is selected.
Set the Starting Month
The Calendar has two placeholder props, placeholder
(controlled), and defaultPlaceholder
(uncontrolled), which are used to change what is displayed in the calendar when no date is selected.
By default, the placeholder will be set to the current date, but you can override this by passing a
DateValue
object to the defaultPlaceholder
prop.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
31 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 1 | 2 | 3 | 4 | 5 | 6 |
Now our calendar starts out in February 2021, rather than the current month.
Values
The Calendar has two value props, value
(controlled), and defaultValue
(uncontrolled), which are
used to determine what date is selected in the calendar.
Setting a Default Value
To have a date selected by default, we can use the value
(controlled), or defaultValue
(uncontrolled) props. If provided, the placeholder props will be set to this value.
The value
is a DateRange
object, which consists of a start
and end
property, each of which
can be a DateValue
object.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
31 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
Using the Value
The value
store returned from the createRangeCalendar
builder is a DateRange
object.
You can use this value to display the selected date in your own markup, or use its value within a form. Although for forms we highly recommend using our Date Range Picker , as it is a combination of the Range Calendar, Date Range Field , and Popover which makes it more similar to a native date picker, but with a lot more power.
Appearance & Behavior
Fixed Weeks
If you press the previous/next buttons a few times on the example below, you'll notice something that may be unappealing. The calendar navigates to the previous month, and since that month has an additional week, the calendar jumps in height.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
31 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
You could use CSS to add the extra space to accommodate for such a jump, or you can set the
fixedWeeks
prop to true
, which will ensure the calendar always has the same number of weeks,
regardless of the month.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
Multiple Months
By default, the calendar will display one month, but it supports displaying as many months as you'd
like, using the numberOfMonths
prop.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
Paged Navigation
By default, when the calendar has more than one month, the previous and next buttons will shift the
calendar forward or backward by one month. However, you can change this behavior by setting the
pagedNavigation
prop to true
, which will shift the calendar forward or backward by the number of
months being displayed.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
Localization
The calendar will automatically format the content of the calendar according to the locale
prop,
which defaults to 'en-US', but can be changed to any locale supported by the
Intl.DateTimeFormat
constructor.
L | M | X | J | V | S | D |
---|---|---|---|---|---|---|
28 | 29 | 30 | 31 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
Validation
Although possible to implement your own validation logic using change functions, the Range Calendar provides a few props that make it a bit easier.
Unavailable Dates
An unavailable date is a date that is not selectable, but is still visible and focusable in the calendar.
You can pass a Matcher
function to the isDateUnavailable
prop, which will be used to determine
if a date is unavailable. The Matcher
function is called with a DateValue
object, and should
return a boolean indicating whether the date is unavailable.
The unavailable dates will have the data-unavailable
attribute set, which you can use to style
differently than the other dates.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
Disabled Dates
Disabled dates are not selectable, nor are they focusable in the calendar. Keyboard navigation will skip over them entirely, and should be used for dates that have no meaning in the context of the calendar.
You can pass a Matcher
function to the isDateDisabled
prop, which will be used to determine if a
date is disabled. The Matcher
function is called with a DateValue
object, and should return a
boolean indicating whether the date is disabled.
In this example, we're disabling the first 10 days of each month.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 31 | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
Minimum & Maximum Values
While the isDateDisabled
prop is useful for more complex logic, the Calendar also provides the
minValue
and maxValue
props, which are used to set the min and max selectable dates, which is
nice if you're just trying to limit the range of dates that can be selected.
If a date is before the minValue
, or after the maxValue
, it will be disabled.
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
API Reference
createRangeCalendar
The builder function used to create the calendar component.
Props
Prop | Default | Type / Description |
defaultValue | - | DateRange | undefined The default value for the calendar. When provided the |
value | - | Writable<DateRange | undefined> A writable store than can be used to control the value of the calendar from outside the builder. Useful if you want to sync the value of the calendar with another store used in your app. |
onValueChange | - | ChangeFn<DateRange | undefined> A function called when the value of the calendar changes. It receives a single argument, which is an object containing |
defaultPlaceholder | CalendarDate | DateValue The date that is used when the calendar is empty to determine what point in time the calendar should start at. |
placeholder | - | Writable<DateValue> A writable store that can be used to control the placeholder date from outside the builder. When this prop is provided, the |
onPlaceholderChange | - | ChangeFn<DateValue> A function called when the placeholder value changes. It receives a single argument, which is an object containing |
isDateUnavailable | - | Matcher | undefined A function that accepts a date and returns a boolean indicating whether the date is unavailable. |
minValue | - | DateValue | undefined The minimum date that can be selected. |
maxValue | - | DateValue | undefined The maximum date that can be selected. |
disabled | false | boolean Whether the calendar is disabled. |
readonly | false | boolean Whether the calendar is readonly. |
locale | 'en' | string The locale to use when formatting the date. |
pagedNavigation | false | boolean Whether to use paged navigation for the calendar. |
weekStartsOn | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 The day of the week the calendar starts on. 0 is Sunday, 6 is Saturday, etc. |
fixedWeeks | false | boolean Whether to always show 6 weeks in the calendar. |
calendarLabel | 'Event date' | string An accessible label for the calendar. |
ids | - | RangeCalendarIds Override the default ids used by the various elements within the calendar. |
Elements
Element | Description |
The container of the months and days of the calendar | |
A visual heading for the calendar | |
A grid representing a month of the calendar | |
A cell representing a single date in the calendar | |
A button which moves the calendar to the next page | |
A button which moves the calendar to the previous page |
States
State | Description |
value | A writable store which represents the current value of the range calendar. |
months | A readable store containing month objects for each month in the calendar. |
weekdays | A readable store containing the days of the week, formatted to the |
headingValue | A readable store containing the heading for the calendar, formatted to the |
placeholder | A writable store which represents the placeholder value of the calendar. |
startValue | A readable store containing the current start value of the calendar, which can exist before the actual |
endValue | A readable store containing the current end value of the calendar, which can exist before the actual |
Helpers
Helper | Description |
nextPage | A function that moves the calendar to the next page. |
prevPage | A function that moves the calendar to the previous page. |
nextYear | A function that moves the calendar to the next year. |
prevYear | A function that moves the calendar to the previous year. |
setYear | A function that sets the year of the calendar. |
setMonth | A function that sets the month of the calendar. |
isDateDisabled | A function that returns whether the given date is disabled. |
isDateUnavailable | A function that returns whether the given date is unavailable. |
isSelected | A function that returns whether the given date is selected. |
isSeletionStart | A function that returns whether the given date is the date at the start of the selection range. |
isSeletionEnd | A function that returns whether the given date is the date at the end of the selection range. |
Options
Option | Description |
defaultValue | The default value for the calendar. When provided the |
onValueChange | A function called when the value of the calendar changes. It receives a single argument, which is an object containing |
defaultPlaceholder | The date that is used when the calendar is empty to determine what point in time the calendar should start at. |
onPlaceholderChange | A function called when the placeholder value changes. It receives a single argument, which is an object containing |
isDateUnavailable | A function that accepts a date and returns a boolean indicating whether the date is unavailable. |
minValue | The minimum date that can be selected. |
maxValue | The maximum date that can be selected. |
disabled | Whether the calendar is disabled. |
readonly | Whether the calendar is readonly. |
locale | The locale to use when formatting the date. |
pagedNavigation | Whether to use paged navigation for the calendar. |
weekStartsOn | The day of the week the calendar starts on. 0 is Sunday, 6 is Saturday, etc. |
fixedWeeks | Whether to always show 6 weeks in the calendar. |
calendarLabel | An accessible label for the calendar. |
ids | Override the default ids used by the various elements within the calendar. |
calendar
The container of the months and days of the calendar
Data Attributes
Data Attribute | Value |
[data-invalid] | Present when the calendar is invalid. |
[data-disabled] | Present when the calendar is disabled. |
[data-readonly] | Present when the calendar is readonly. |
[data-melt-calendar] | Present on all calendar elements. |
Custom Events
Event | Value |
m-keydown | (e: ) => void |
grid
A grid representing a month of the calendar
Data Attributes
Data Attribute | Value |
[data-disabled] | Present when the calendar is disabled. |
[data-melt-calendar-grid] | Present on all grid elements. |
cell
A cell representing a single date in the calendar
Data Attributes
Data Attribute | Value |
[data-disabled] | Present when the date is disabled. |
[data-selected] | Present when the date is selected. |
[data-value] | The ISO string value of the date. |
[data-unavailable] | Present when the date is unavailable. |
[data-today] | Present when the date is today. |
[data-outside-month] | Present when the date is outside the current month it is displayed in. |
[data-outside-visible-months] | Present when the date is outside the months that are visible on the calendar. |
[data-selection-start] | Present when the date is the start of the selection. |
[data-selection-end] | Present when the date is the end of the selection. |
[data-highlighted] | Present when the date is highlighted by the user as they select a range. |
[data-focused] | Present when the date is focused. |
[data-melt-calendar-cell] | Present on all cell elements. |
Custom Events
Event | Value |
m-click | (e: ) => void |
m-mouseenter | (e: ) => void |
m-focusin | (e: ) => void |
heading
A visual heading for the calendar
Data Attributes
Data Attribute | Value |
[data-invalid] | Present when the calendar is invalid. |
[data-disabled] | Present when the calendar is disabled. |
[data-melt-calendar-heading] | Present on all heading elements. |
prevButton
A button which moves the calendar to the previous page
Data Attributes
Data Attribute | Value |
[data-disabled] | Present when the calendar is disabled. |
[data-melt-calendar-prevButton] | Present on all prevButton elements. |
Custom Events
Event | Value |
m-click | (e: ) => void |
nextButton
A button which moves the calendar to the next page
Data Attributes
Data Attribute | Value |
[data-disabled] | Present when the calendar is disabled. |
[data-melt-calendar-nextButton] | Present on all nextButton elements. |
Custom Events
Event | Value |
m-click | (e: ) => void |
On This Page