Date Range Picker

Dropdown calendar for picking a start and end date, with presets and granularity options.

Basic

.heex
<.date_range_picker start_name="start" end_name="end" />

Label and sublabel

Required
.heex
<.date_range_picker
  start_name="trip_start"
  end_name="trip_end"
  label="Trip dates"
  sublabel="Required"
/>

Description and help text

Use description for the rule above the field and help_text for the hint below it.

Check-in is after 3pm; check-out before 11am.

Up to 30 nights per booking.
.heex
<.date_range_picker
  start_name="stay_start"
  end_name="stay_end"
  label="Stay dates"
  description="Check-in is after 3pm; check-out before 11am."
  help_text="Up to 30 nights per booking."
/>

Pre-selected range

Pass start_value and end_value as Date, NaiveDateTime, DateTime, or ISO 8601 strings.

.heex
<.date_range_picker
  start_name="report_start"
  end_name="report_end"
  start_value={Date.utc_today()}
  end_value={Date.add(Date.utc_today(), 6)}
/>

Sizes

.heex
<.date_range_picker start_name="start" end_name="end" size="xs" />
<.date_range_picker start_name="start" end_name="end" size="sm" />
<.date_range_picker start_name="start" end_name="end" size="md" />
<.date_range_picker start_name="start" end_name="end" size="lg" />
<.date_range_picker start_name="start" end_name="end" size="xl" />

Disabled

.heex
<.date_range_picker
  start_name="start"
  end_name="end"
  start_value={Date.utc_today()}
  end_value={Date.add(Date.utc_today(), 3)}
  disabled
/>

Required range

allow_deselect={false} keeps an existing range from being cleared by clicking its start.

.heex
<.date_range_picker
  start_name="contract_start"
  end_name="contract_end"
  label="Contract period"
  start_value={Date.utc_today()}
  end_value={Date.add(Date.utc_today(), 14)}
  allow_deselect={false}
/>

Clearable

Set clearable to render a reset button inside the field. Both endpoints reset together, and clearing dispatches a change event so phx-change bindings are notified.

.heex
<.date_range_picker
  start_name="trip_start"
  end_name="trip_end"
  label="Trip dates"
  start_value={Date.utc_today()}
  end_value={Date.add(Date.utc_today(), 7)}
  clearable
/>

Error state

.heex
<.date_range_picker
  start_name="window_start"
  end_name="window_end"
  label="Reporting window"
  errors={["End date must be after the start date"]}
/>

Leading icon

Use :inner_prefix to render an icon inside the field's border.

.heex
<.date_range_picker
  start_name="event_start"
  end_name="event_end"
  placeholder="Select range"
>
  <:inner_prefix>
    <.icon name="hero-calendar" class="icon" />
  </:inner_prefix>
</.date_range_picker>

Outer prefix label

:outer_prefix renders an attached cell that shares the field's border.

Period
.heex
<.date_range_picker
  start_name="filter_start"
  end_name="filter_end"
  placeholder="Select range"
>
  <:outer_prefix class="px-3 text-foreground-soft">Period</:outer_prefix>
</.date_range_picker>

Min and max

Constrain the selectable window. Calendar navigation disables past the boundaries.

.heex
<.date_range_picker
  start_name="booking_start"
  end_name="booking_end"
  min={Date.utc_today()}
  max={Date.add(Date.utc_today(), 90)}
/>

Weekdays only

Use the :weekends shortcut to skip Saturdays and Sundays.

.heex
<.date_range_picker
  start_name="campaign_start"
  end_name="campaign_end"
  disabled_dates={[:weekends]}
/>

Holiday calendar

Combine specific dates with recurring annual patterns. Disabled days are dimmed and struck through.

.heex
<.date_range_picker
  start_name="ship_start"
  end_name="ship_end"
  disabled_dates={[
    ~D[2026-12-24],
    ~D[2026-12-31],
    {:month_day, 12, 25},
    {:month_day, 1, 1}
  ]}
/>

Display format

Both endpoints share the same strftime pattern in the toggle.

.heex
<.date_range_picker
  start_name="conf_start"
  end_name="conf_end"
  start_value={Date.utc_today()}
  end_value={Date.add(Date.utc_today(), 4)}
  display_format="%b %-d, %Y"
/>

Month granularity

Set granularity="month" for a 3-column month grid. Endpoints normalize to the first of the month.

.heex
<.date_range_picker
  start_name="quarter_start"
  end_name="quarter_end"
  granularity="month"
/>

Adds previous/next year arrows alongside the month arrows for ranges that span years.

.heex
<.date_range_picker
  start_name="start"
  end_name="end"
  navigation="extended"
/>

Multi-month layout

months (1..4) renders calendar grids side-by-side. The visible count adapts to the viewport.

.heex
<.date_range_picker
  start_name="check_in"
  end_name="check_out"
  label="Stay dates"
  months={2}
/>

navigation="select" adds month and year dropdowns beside the arrows for jumping across years.

.heex
<.date_range_picker
  start_name="history_start"
  end_name="history_end"
  navigation="select"
  min={~D[2000-01-01]}
  max={Date.utc_today()}
/>

Week starts on Monday

Shifts the first column of the day grid. Defaults to Sunday; set to 1 for ISO 8601.

.heex
<.date_range_picker
  start_name="start"
  end_name="end"
  week_start={1}
/>

Confirm mode

close="confirm" shows Cancel and Apply buttons; the range commits only on Apply.

.heex
<.date_range_picker
  start_name="start"
  end_name="end"
  close="confirm"
/>

Range presets

Render a sidebar of one-click range shortcuts. Each preset is a three-tuple {label, start, end}.

.heex
<.date_range_picker
  start_name="analytics_start"
  end_name="analytics_end"
  presets={[
    {"Today", Date.utc_today(), Date.utc_today()},
    {"Yesterday", Date.add(Date.utc_today(), -1), Date.add(Date.utc_today(), -1)},
    {"Last 7 days", Date.add(Date.utc_today(), -6), Date.utc_today()},
    {"Last 30 days", Date.add(Date.utc_today(), -29), Date.utc_today()},
    {"This month", Date.beginning_of_month(Date.utc_today()), Date.utc_today()}
  ]}
/>

Spanish locale

locale translates weekday and month names. Translate your own labels separately, for example via Gettext.

.heex
<.date_range_picker
  start_name="es_start"
  end_name="es_end"
  label="Periodo"
  placeholder="Selecciona un periodo"
  locale="es"
/>

German locale

German pulls in dd.mm.yyyy formatting and a Monday week start automatically.

.heex
<.date_range_picker
  start_name="de_start"
  end_name="de_end"
  label="Zeitraum"
  locale="de"
  navigation="select"
/>

Form integration

Changes: %{}
.heex
<.form :let={f} for={@form} phx-change="validate">
  <.date_range_picker
    start_field={f[:departure_date]}
    end_field={f[:return_date]}
    min={Date.utc_today()}
    max={Date.add(Date.utc_today(), 365)}
    navigation="extended"
    placeholder="Flight date"
  />
</.form>
.ex
def handle_event("validate", %{"data" => _params}, socket) do
  # Re-run validation and reassign the form
  {:noreply, socket}
end