An enhanced alternative to using multiple native date inputs for selecting a date range.
(Not a complete list)
Booking Dates
mm
/
dd
/
yyyy
-
mm
/
dd
/
yyyy
Features
Full keyboard navigation
Localization support
Can be controlled or uncontrolled
Focus is fully managed
Accessible by default
Supports both date and date-time formats
Overview
The Date Range Field is an enhanced alternative to the rather limited date range selection setup
using two native inputs. It provides a more user-friendly interface for selecting dates and times,
is fully accessible, and works in all modern browsers & devices.
Before jumping into the docs for this builder, it's recommended that you understand how we work with dates &
times in Melt, which you can read about here.
Anatomy
Field: The element which contains the date segments
Start Segment: An individual segment of the start date (day, month, year, etc.)
End Segment: An individual segment of the end date (day, month, year, etc.)
Label: The label for the date field
Start Hidden Input: A hidden input element containing the ISO 8601 formatted string of the
start date
End Hidden Input: A hidden input element containing the ISO 8601 formatted string of the end
date
Tutorial
Learn how to use the Date Range Field builder by starting simple and working your way up to more
complex examples and use cases. The goal is to teach you the key features and concepts of the
builder, so you won’t have to read through a bunch of API reference docs (Although, those are
available too!)
Building a Date Range Field
Let's initialize our field using the createDateRangeField function, which returns an object
consisting of the stores & methods needed to construct the field.
To start off, we'll destructure the field, startSegment, endSegment, label, and
segmentContents.
The field is responsible for containing the date segments. The label for the date field is not
an actual <label> element, due to the way we interact with the field, but is still accessible to
screen readers in the same way.
Unlike the other elements, startSegment & endSegment are functions which take a SegmentPart
as an argument, which is used to determine which segment this element represents.
While it's possible to use the start and end functions to render each segment individually like so:
It's not recommended, as the formatting doesn't adapt to the locale and type of date being
represented, which is one of the more powerful features this builder provides.
Instead, you can use the segmentContents state, which is an object containing start and end
properties, whose values are an array of objects necessary to form the date. Each object has a
part property, which is the `SegmentPart`, and a value property, which is
the locale-aware string representation of the segment.
To actually use the value of the field, you can either use the value state directly, or if you're
using it within a form, the startHiddenInput & endHiddenInput elements, which are hidden input
elements containing an ISO 8601 formatted string of the value that input represents.
If you plan on using the hidden inputs, you'll need to pass the startName and endName props,
which will be used as the respective input's name attribute.
And that, along with some additional structure and styles, is all you need to get a fully functional
Date Range Field!
The Power of Placeholder
In the previous example, we didn't pass any date-related props to the createDateRangeField
function, which means it defaulted to a CalendarDate object with the current date. What if we
wanted to start off with a different type of date, such as a CalendarDateTime or a
ZonedDateTime, but keep the initial value empty?
That's where the placeholder props come in, which consist of the uncontrolled defaultPlaceholder
prop, and the controlled placeholder prop.
In the absense of a value or defaultValue prop (which we'll cover soon), the placeholder holds
all the power in determining what type of date the field represents, and how it should be
rendered/formatted.
Let's convert our previous example into a Date & Time field, by passing a CalendarDateTime object
as the defaultPlaceholder prop.
Trip Dates
mm
/
dd
/
yyyy
,
––
:
––
 
PM
-
mm
/
dd
/
yyyy
,
––
:
––
 
PM
As you can see above, by making that one change, the field now represents date and times, and the
segments have been updated to reflect that.
If your placeholder value is coming from a database or elsewhere, you can use one of the parser
functions provided by
@internationalized/date to
convert it into a CalendarDateTime object.
We can also just as easily convert the field into a Zoned Date & Time field, by passing a
ZonedDateTime object as the defaultPlaceholder prop.
We're using the now parser function to create a ZonedDateTime object with the current date and
time, and we're getting the user's local timezone using the getLocalTimeZone function.
Trip Dates
mm
/
dd
/
yyyy
,
––
:
––
 
AM
UTC
-
mm
/
dd
/
yyyy
,
––
:
––
 
AM
UTC
Alternatively, we can hardcode the timezone to something like America/Los_Angeles by passing it as
the argument to the now function.
Trip Dates
mm
/
dd
/
yyyy
,
––
:
––
 
PM
PST
-
mm
/
dd
/
yyyy
,
––
:
––
 
PM
PST
How you represent and store dates with timezones will depend entirely on your use case, but there
are a number of parser functions available to help you out, which you can read more about
here .
Working with Values
Now that we've covered the basics of the Date Range Field, as well as the power of the placeholder,
let's look at how we can use the defaultValue to set the value of the field.
Remember, the placeholder props are only relevant in the absense of a value or defaultValue
prop, as they take precedence over the placeholder. When a value or defaultValue prop is passed,
the placeholder is set to the end value of those props.
We can demonstrate this by keeping the defaultPlaceholder prop as a CalendarDate object, and
passing a DateRange using CalendarDateTime objects as the defaultValue prop. It's not
recommended that you ever these to to different types, but it's useful to understand how the
placeholder & value props interact.
Trip Dates
10
/
11
/
2023
,
12
:
30
 
PM
-
10
/
15
/
2023
,
12
:
30
 
PM
As you can see, the field represents a CalendarDateTime object, and even if you clear the field,
it will retain that shape.
A really useful scenario for using the defaultValue prop and the defaultPlaceholder prop in
conjunction with one another is when a defaultValue may or may not be present, and you want to
ensure the field always represents a certain type of date.
For example, let's say this field is selecting a contact availability range in a user's profile,
which is optional, but you want to ensure that if they do enter a range, it's represented as a
CalendarDateTime object. The code to accomplishing that may look something like this:
If the user has those availability settings, we parse the ISO 8601 formatted string into a
CalendarDateTime object, and if not, we pass undefined for the start and end values, which will
cause the field to default to the defaultPlaceholder prop, which we've set to a CalendarDate
object.
The following example demonstrates how it would work in both scenarios (with and without
availability dates).
Availability
mm
/
dd
/
yyyy
,
––
:
––
 
AM
-
mm
/
dd
/
yyyy
,
––
:
––
 
AM
Availability
10
/
11
/
2021
,
12
:
00
 
AM
-
10
/
15
/
2021
,
12
:
00
 
AM
Situations like this make using the defaultValue and defaultPlaceholder props together a must.
Validating Dates
This is where things start to get a lot more fun! This builder provides a few ways to validate
dates, which we'll cover in this section, starting with the isDateUnavailable prop.
The isDateUnavailable prop is a Matcher function, which takes a DateValue object as an
argument, and returns a boolean indicating whether or not that date is unavailable.
If the date the user selects is unavailable, is marked as invalid, and you can do whatever you'd
like with that information.
Let's say that we don't want users to ever be able to select the 1st or the 15th of any month, as
those are the only two days we're not working on builder tutorials. We can setup a Matcher
function to accomplish just that.
If you have a few different matchers you want to use, you can simply combine them like so:
Or if you want to get really fancy with it, you can create a helper function that takes an array of
matchers which you could use throughout your app.
When a field is marked as invalid, the isInvalid store will be set to true, and a data-invalid
attribute will be added to all the elements that make up the field, which you can use to style the
field however you'd like.
You'll want to use the validation element to display a message to the user indicating why the date
is invalid. It's automatically hidden when the field is valid, and is wired up via aria attributes
to give screen readers the information they need.
Here's an example to get an idea of what you might do. Attempt to enter an unavailable date, and
you'll see the behavior in action.
Availability
10
/
11
/
2023
-
10
/
16
/
2023
Selected range must not contain the 1st or 15th.
The Date Field builder also accepts minValue and maxValue props to set the minimum and maximum
dates a user can select.
In this example, we're limiting the selection dates to between October 11th, 2023 and October
11th, 2024.
Availability
10
/
11
/
2023
-
10
/
13
/
2023
Date must be between 2023-10-11 & 2024-10-11.
If you increment the year of the end date to 2024, you'll see the validation message appear, as the
range exceeds the maximum date.
Locale-aware Formatting
One of the coolest features of this builder is the ability to automatically format the segments and
placeholder based on the locale.
Of course it's up to you to decide how you get your user's locale, but once you have it, it's as
simple as passing it as the locale prop.
Here's an example showcasing a few different locales:
Availability
mm
/
dd
/
yyyy
,
––
:
––
 
AM
-
mm
/
dd
/
yyyy
,
––
:
––
 
AM
Availability
dd
/
mm
/
yyyy
,
––
:
––
-
dd
/
mm
/
yyyy
,
––
:
––
Availability
tt
.
mm
.
jjjj
,
––
:
––
-
tt
.
mm
.
jjjj
,
––
:
––
Availability
jj
/
mm
/
aaaa
––
:
––
-
jj
/
mm
/
aaaa
––
:
––
Notice that they all have the same defaultPlaceholder, yet the segments are formatted differently
depending on the locale, and all it took was changing the locale prop. Pretty cool, right?
Readonly Segments
Like the Date Field builder, Date Range Fields can be set as readonly using the readonly prop. But
in some situations, you may want a user to be able to edit the time but not the date, or the month
but not the year. You can configure which segments are readonly for the start and end fields
separately using the readonlySegments prop.
In this example, the year segment is readonly on both start and end fields, month is also
readonly on start, but the day segment is focusable and editable on both.
Trip Dates
10
/
11
/
2023
-
10
/
12
/
2023
This could be used to ensure the end year can not be set different from the start year, using a
controlled value prop, for example.
API Reference
createDateRangeField
The builder function used to create the date field component.
Props
Prop
Default
Type / Description
defaultValue
-
DateRange | undefined
The default value for the date range field. When provided the placeholder will assume the start date of the default value.
value
-
Writable<DateRange | undefined>
A writable store than can be used to control the value of the date range field from outside the builder. Useful if you want to sync the value of the date field with another store used in your app.
onValueChange
-
ChangeFn<DateRange | undefined>
A function called when the value of the date range field changes. It receives a single argument, which is an object containing curr and prev properties, whose values are the current and previous values of the value store. Whatever you return from this function will be set as the new value of the value store.
defaultPlaceholder
CalendarDate
DateValue
The date that is used when the date range field is empty to determine what point in time the field 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 defaultPlaceholder prop is ignored, and the value of this store is used instead.
onPlaceholderChange
-
ChangeFn<DateValue>
A function called when the placeholder value changes. It receives a single argument, which is an object containing curr and prev properties, whose values are the current and previous values of the placeholder store. Whatever you return from this function will be set as the new value of the placeholder store.
isDateUnavailable
-
Matcher | undefined
A function that accepts a date and returns a boolean indicating whether the date is unavailable.
The sets of segments that are readonly on the start and end fields.
hourCycle
-
HourCycle
The hour cycle to use when formatting the date.
locale
'en'
string
The locale to use when formatting the date.
granularity
-
'day' | 'hour' | 'minute' | 'second'
The granularity of the date range field. Defaults to 'day' if a CalendarDate is provided, otherwise defaults to 'minute'. The field will render segments for each part of the date up to and including the specified granularity.
name
-
string
The name of the hidden input element.
required
false
boolean
Whether the hidden input is required.
ids
-
DateRangeFieldIds
Override the default ids used by the various elements within the date range field.
The hidden input used to submit the end value within a form
States
State
Description
value
A writable store which represents the current value of the date field.
segmentValues
An object of writable stores containing the current values of the date segments.
segmentContents
An object of readable stores used to dynamically render the date segments.
segmentContentsObj
An object of readable stores containing the current values of the date segments.
placeholder
A writable store which represents the placeholder value of the date field.
isInvalid
A readable store which represents whether the date field is invalid.
isDateUnavailable
A readable store which returns a function that accepts a date and returns a boolean indicating whether the date is unavailable.
Options
Option
Description
defaultValue
The default value for the date range field. When provided the placeholder will assume the start date of the default value.
onValueChange
A function called when the value of the date range field changes. It receives a single argument, which is an object containing curr and prev properties, whose values are the current and previous values of the value store. Whatever you return from this function will be set as the new value of the value store.
defaultPlaceholder
The date that is used when the date range field is empty to determine what point in time the field should start at.
onPlaceholderChange
A function called when the placeholder value changes. It receives a single argument, which is an object containing curr and prev properties, whose values are the current and previous values of the placeholder store. Whatever you return from this function will be set as the new value of the placeholder store.
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 date field is disabled.
readonly
Whether the date field is readonly.
readonlySegments
The sets of segments that are readonly on the start and end fields.
hourCycle
The hour cycle to use when formatting the date.
locale
The locale to use when formatting the date.
granularity
The granularity of the date range field. Defaults to 'day' if a CalendarDate is provided, otherwise defaults to 'minute'. The field will render segments for each part of the date up to and including the specified granularity.
name
The name of the hidden input element.
required
Whether the hidden input is required.
ids
Override the default ids used by the various elements within the date range field.