Date Range Field

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField()
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField()
</script>
	

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField()
</script>
 
<span use:melt={$label}>Trip Dates</span>
<div use:melt={$field}>
  <!-- ... -->
</div>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField()
</script>
 
<span {...$label} use:label>Trip Dates</span>
<div {...$field} use:field>
  <!-- ... -->
</div>
	

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:

    <span use:melt={$label}>Trip Date</span>
<div use:melt={$field}>
  <div use:melt={$startSegment('day')}>
    <!-- ... -->
  </div>
  <div use:melt={$startSegment('month')}>
    <!-- ... -->
  </div>
  <div use:melt={$startSegment('year')}>
    <!-- ... -->
  </div>
  <div use:melt={$endSegment('day')}>
    <!-- ... -->
  </div>
  <!-- ...rest -->
</div>
	
    <span {...$label} use:label>Trip Date</span>
<div {...$field} use:field>
  <div {...$startSegment('day')} use:startSegment>
    <!-- ... -->
  </div>
  <div {...$startSegment('month')} use:startSegment>
    <!-- ... -->
  </div>
  <div {...$startSegment('year')} use:startSegment>
    <!-- ... -->
  </div>
  <div {...$endSegment('day')} use:endSegment>
    <!-- ... -->
  </div>
  <!-- ...rest -->
</div>
	

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.
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField()
</script>
 
<span use:melt={$label}>Trip Dates</span>
<div use:melt={$field}>
  {#each $segmentContents.start as seg, i (i)}
    <div use:melt={$startSegment(seg.part)}>
      {seg.value}
    </div>
  {/each}
  <div aria-hidden="true">-</div>
  {#each $segmentContents.end as seg, i (i)}
    <div use:melt={$endSegment(seg.part)}>
      {seg.value}
    </div>
  {/each}
</div>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField()
</script>
 
<span {...$label} use:label>Trip Dates</span>
<div {...$field} use:field>
  {#each $segmentContents.start as seg, i (i)}
    <div {...$startSegment(seg.part)} use:startSegment>
      {seg.value}
    </div>
  {/each}
  <div aria-hidden="true">-</div>
  {#each $segmentContents.end as seg, i (i)}
    <div {...$endSegment(seg.part)} use:endSegment>
      {seg.value}
    </div>
  {/each}
</div>
	

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.

    <script lang="ts">
  import { createDateField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label, startHiddenInput, endHiddenInput },
    states: { segmentContents, value }
  } = createDateField({
    startName: 'tripStart',
    endName: 'tripEnd'
  })
</script>
 
<form method="POST">
  <span use:melt={$label}>Trip Dates</span>
  <div use:melt={$field}>
    {#each $segmentContents.start as seg, i (i)}
      <div use:melt={$startSegment(seg.part)}>
        {seg.value}
      </div>
    {/each}
    <div aria-hidden="true">-</div>
    {#each $segmentContents.end as seg, i (i)}
      <div use:melt={$endSegment(seg.part)}>
        {seg.value}
      </div>
    {/each}
    <input use:melt={$startHiddenInput} />
    <input use:melt={$endHiddenInput} />
  </div>
  <p>
    You selected:
    {#if $value}
      {$value}
    {/if}
  </p>
</form>
	
    <script lang="ts">
  import { createDateField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label, startHiddenInput, endHiddenInput },
    states: { segmentContents, value }
  } = createDateField({
    startName: 'tripStart',
    endName: 'tripEnd'
  })
</script>
 
<form method="POST">
  <span {...$label} use:label>Trip Dates</span>
  <div {...$field} use:field>
    {#each $segmentContents.start as seg, i (i)}
      <div {...$startSegment(seg.part)} use:startSegment>
        {seg.value}
      </div>
    {/each}
    <div aria-hidden="true">-</div>
    {#each $segmentContents.end as seg, i (i)}
      <div {...$endSegment(seg.part)} use:endSegment>
        {seg.value}
      </div>
    {/each}
    <input {...$startHiddenInput} use:startHiddenInput />
    <input {...$endHiddenInput} use:endHiddenInput />
  </div>
  <p>
    You selected:
    {#if $value}
      {$value}
    {/if}
  </p>
</form>
	

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDateTime } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: new CalendarDateTime(2023, 10, 11, 12, 30)
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDateTime } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: new CalendarDateTime(2023, 10, 11, 12, 30)
  })
</script>
	
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.

    import { CalendarDateTime, parseDateTime } from '@internationalized/date'
 
// Constructor with a time
const date = new CalendarDateTime(2023, 10, 11, 12, 30)
 
// Constructor without a time (defaults to 00:00:00)
const date = new CalendarDateTime(2023, 10, 11)
 
// Parser function to convert an ISO 8601 formatted string
const date = parseDateTime('2023-10-11T12:30:00')
	
    import { CalendarDateTime, parseDateTime } from '@internationalized/date'
 
// Constructor with a time
const date = new CalendarDateTime(2023, 10, 11, 12, 30)
 
// Constructor without a time (defaults to 00:00:00)
const date = new CalendarDateTime(2023, 10, 11)
 
// Parser function to convert an ISO 8601 formatted string
const date = parseDateTime('2023-10-11T12:30:00')
	

We can also just as easily convert the field into a Zoned Date & Time field, by passing a ZonedDateTime object as the defaultPlaceholder prop.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { now, getLocalTimeZone } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: now(getLocalTimeZone())
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { now, getLocalTimeZone } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: now(getLocalTimeZone())
  })
</script>
	

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { now } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: now('America/Los_Angeles')
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { now } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: now('America/Los_Angeles')
  })
</script>
	
Trip Dates
mm
/
dd
/
yyyy
,
––
:
––
 
AM
PDT
mm
/
dd
/
yyyy
,
––
:
––
 
AM
PDT

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDateTime, CalendarDate } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: new CalendarDate(2023, 10, 11),
    defaultValue: {
      start: new CalendarDateTime(2023, 10, 11, 12, 30),
      end: new CalendarDateTime(2023, 10, 15, 12, 30)
    }
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDateTime, CalendarDate } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: new CalendarDate(2023, 10, 11),
    defaultValue: {
      start: new CalendarDateTime(2023, 10, 11, 12, 30),
      end: new CalendarDateTime(2023, 10, 15, 12, 30)
    }
  })
</script>
	
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:

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDateTime, CalendarDate, parseDateTime } from '@internationalized/date'
 
  export let data
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: new CalendarDate(2023, 10, 11),
    defaultValue: {
      start: data?.userAvailabilityStart ? parseDateTime(data?.userAvailabilityStart) : undefined,
      end: data?.userAvailabilityEnd ? parseDateTime(data?.userAvailabilityEnd) : undefined
    }
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDateTime, CalendarDate, parseDateTime } from '@internationalized/date'
 
  export let data
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultPlaceholder: new CalendarDate(2023, 10, 11),
    defaultValue: {
      start: data?.userAvailabilityStart ? parseDateTime(data?.userAvailabilityStart) : undefined,
      end: data?.userAvailabilityEnd ? parseDateTime(data?.userAvailabilityEnd) : undefined
    }
  })
</script>
	

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.

    type Matcher = (date: DateValue) => boolean
	
    type Matcher = (date: DateValue) => boolean
	

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.

    <script lang="ts">
  import { createDateField, melt, type Matcher } from '@melt-ui/svelte'
 
  const isFirstOrFifteenth: Matcher = (date) => {
    return date.day === 1 || date.day === 15
  }
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { value, segmentContents, isInvalid }
  } = createDateField({
    isDateUnavailable: isFirstOrFifteenth
  })
</script>
	
    <script lang="ts">
  import { createDateField, melt, type Matcher } from '@melt-ui/svelte'
 
  const isFirstOrFifteenth: Matcher = (date) => {
    return date.day === 1 || date.day === 15
  }
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { value, segmentContents, isInvalid }
  } = createDateField({
    isDateUnavailable: isFirstOrFifteenth
  })
</script>
	

If you have a few different matchers you want to use, you can simply combine them like so:

    <script lang="ts">
  import { createDateField, melt, type Matcher } from '@melt-ui/svelte'
 
  const isFirstOrFifteenth: Matcher = (date) => {
    return date.day === 1 || date.day === 15
  }
 
  const isWeekend: Matcher = (date) => {
    return date.dayOfWeek === 0 || date.dayOfWeek === 6
  }
 
  const isDateUnavailable: Matcher = (date) => {
    return isFirstOrFifteenth(date) || isWeekend(date)
  }
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { value, segmentContents, isInvalid }
  } = createDateField({
    isDateUnavailable
  })
</script>
	
    <script lang="ts">
  import { createDateField, melt, type Matcher } from '@melt-ui/svelte'
 
  const isFirstOrFifteenth: Matcher = (date) => {
    return date.day === 1 || date.day === 15
  }
 
  const isWeekend: Matcher = (date) => {
    return date.dayOfWeek === 0 || date.dayOfWeek === 6
  }
 
  const isDateUnavailable: Matcher = (date) => {
    return isFirstOrFifteenth(date) || isWeekend(date)
  }
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { value, segmentContents, isInvalid }
  } = createDateField({
    isDateUnavailable
  })
</script>
	

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.

    <script lang="ts">
  import { createDateField, melt, type Matcher } from '@melt-ui/svelte'
 
  const isFirstOrFifteenth: Matcher = (date) => {
    return date.day === 1 || date.day === 15
  }
 
  const isWeekend: Matcher = (date) => {
    return date.dayOfWeek === 0 || date.dayOfWeek === 6
  }
 
  const matchers = [isFirstOrFifteenth, isWeekend]
 
  const isDateUnavailable: (...matchers: Matcher[]) => Matcher = (...matchers) => {
    return (date) => {
      return matchers.some((matcher) => matcher(date))
    }
  }
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { value, segmentContents, isInvalid }
  } = createDateField({
    isDateUnavailable
  })
</script>
	
    <script lang="ts">
  import { createDateField, melt, type Matcher } from '@melt-ui/svelte'
 
  const isFirstOrFifteenth: Matcher = (date) => {
    return date.day === 1 || date.day === 15
  }
 
  const isWeekend: Matcher = (date) => {
    return date.dayOfWeek === 0 || date.dayOfWeek === 6
  }
 
  const matchers = [isFirstOrFifteenth, isWeekend]
 
  const isDateUnavailable: (...matchers: Matcher[]) => Matcher = (...matchers) => {
    return (date) => {
      return matchers.some((matcher) => matcher(date))
    }
  }
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { value, segmentContents, isInvalid }
  } = createDateField({
    isDateUnavailable
  })
</script>
	

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.

    <span use:melt={$label}>Availability</span>
<div use:melt={$field}>
  {#each $segmentContents.start as seg, i (i)}
    <div use:melt={$startSegment(seg.part)}>
      {seg.value}
    </div>
  {/each}
  <div aria-hidden="true">-</div>
  {#each $segmentContents.end as seg, i (i)}
    <div use:melt={$endSegment(seg.part)}>
      {seg.value}
    </div>
  {/each}
</div>
<small use:melt={$validation}> Date cannot be on the 1st or 15th of the month. </small>
	
    <span {...$label} use:label>Availability</span>
<div {...$field} use:field>
  {#each $segmentContents.start as seg, i (i)}
    <div {...$startSegment(seg.part)} use:startSegment>
      {seg.value}
    </div>
  {/each}
  <div aria-hidden="true">-</div>
  {#each $segmentContents.end as seg, i (i)}
    <div {...$endSegment(seg.part)} use:endSegment>
      {seg.value}
    </div>
  {/each}
</div>
<small {...$validation} use:validation> Date cannot be on the 1st or 15th of the month. </small>
	

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDate } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { segmentContents }
  } = createDateRangeField({
    defaultValue: {
      start: new CalendarDate(2023, 10, 11),
      end: new CalendarDate(2023, 10, 13)
    },
    minValue: new CalendarDate(2023, 10, 11),
    maxValue: new CalendarDate(2024, 10, 11)
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
  import { CalendarDate } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { segmentContents }
  } = createDateRangeField({
    defaultValue: {
      start: new CalendarDate(2023, 10, 11),
      end: new CalendarDate(2023, 10, 13)
    },
    minValue: new CalendarDate(2023, 10, 11),
    maxValue: new CalendarDate(2024, 10, 11)
  })
</script>
	

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

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { segmentContents }
  } = createDateRangeField({
    locale: 'de'
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '@melt-ui/svelte'
 
  const {
    elements: { field, startSegment, endSegment, label, validation },
    states: { segmentContents }
  } = createDateRangeField({
    locale: 'de'
  })
</script>
	

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.

    <script lang="ts">
  import { createDateRangeField, melt } from '$lib'
  import { CalendarDate } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultValue: {
      start: new CalendarDate(2023, 10, 11),
      end: new CalendarDate(2023, 10, 12)
    },
    readonlySegments: {
      start: ['month', 'year'],
      end: ['year']
    }
  })
</script>
	
    <script lang="ts">
  import { createDateRangeField, melt } from '$lib'
  import { CalendarDate } from '@internationalized/date'
 
  const {
    elements: { field, startSegment, endSegment, label },
    states: { segmentContents }
  } = createDateRangeField({
    defaultValue: {
      start: new CalendarDate(2023, 10, 11),
      end: new CalendarDate(2023, 10, 12)
    },
    readonlySegments: {
      start: ['month', 'year'],
      end: ['year']
    }
  })
</script>
	

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.

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 date field is disabled.

readonly false
boolean

Whether the date field is readonly.

readonlySegments -
{ start: EditableSegmentPart[], end: EditableSegmentPart[] }

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.

Elements

Element Description

The element which contains the date segments

An individual segment of the start date

An individual segment of the end date

The label for the date field

The element containing the validation message

The hidden input used to submit the start value within a form

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.

field

The element which contains the date segments

Data Attributes

Data Attribute Value
[data-invalid]

Present when the date field is invalid.

[data-disabled]

Present when the date field is disabled.

[data-melt-datefield-field]

Present on all field elements.

startSegment

The start segment for the date range field

Data Attributes

Data Attribute Value
[data-invalid]

Present when the field is invalid.

[data-disabled]

Present when the field is disabled.

[data-segment]

SegmentPart

[data-melt-datefield-segment]

Present on all segment elements.

Custom Events

Event Value
m-keydown (e: ) => void
m-focusout (e: ) => void
m-click (e: ) => void

endSegment

The end segment for the date range field

Data Attributes

Data Attribute Value
[data-invalid]

Present when the field is invalid.

[data-disabled]

Present when the field is disabled.

[data-segment]

SegmentPart

[data-melt-datefield-segment]

Present on all segment elements.

Custom Events

Event Value
m-keydown (e: ) => void
m-focusout (e: ) => void
m-click (e: ) => void

label

The label for the date field

Data Attributes

Data Attribute Value
[data-invalid]

Present when the field is invalid.

[data-melt-datefield-label]

Present on all label elements.

validation

The element containing the validation message

Data Attributes

Data Attribute Value
[data-invalid]

Present when the field is invalid.

[data-melt-datefield-validation]

Present on all validation elements.

startHiddenInput

The hidden input for the start date field.

Data Attributes

Data Attribute Value
[data-melt-datefield-hiddenInput]

Present on all hiddenInput elements.

endHiddenInput

The hidden input for the start date field.

Data Attributes

Data Attribute Value
[data-melt-datefield-hiddenInput]

Present on all hiddenInput elements.