Date Picker

Dropdown calendar for date selection, with single or multiple modes and day, month, or year granularity.

Basic

.heex
<.date_picker name="date" />

Label and sublabel

Required
.heex
<.date_picker name="event_date" label="Event date" sublabel="Required" />

Description and help text

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

We will confirm by email within one business day.

Pick any weekday in the next month.
.heex
<.date_picker
  name="appointment"
  label="Appointment date"
  description="We will confirm by email within one business day."
  help_text="Pick any weekday in the next month."
/>

Custom placeholder

.heex
<.date_picker name="due_date" placeholder="Choose a due date" />

Pre-selected value

Pass a Date, NaiveDateTime, DateTime, or ISO 8601 string.

.heex
<.date_picker name="kickoff" value={Date.utc_today()} />

Sizes

.heex
<.date_picker name="date" size="xs" />
<.date_picker name="date" size="sm" />
<.date_picker name="date" size="md" />
<.date_picker name="date" size="lg" />
<.date_picker name="date" size="xl" />

Disabled

.heex
<.date_picker name="locked" value={Date.utc_today()} disabled />

Required selection

allow_deselect={false} prevents clearing the value once a date is chosen.

.heex
<.date_picker
  name="contract_date"
  label="Contract date"
  value={Date.utc_today()}
  allow_deselect={false}
/>

Clearable

Set clearable to render a reset button inside the field. It only appears once a value is present, and clearing dispatches a change event so phx-change bindings are notified. Works in every mode. Multiple selection clears every date.

.heex
<.date_picker name="renewal" label="Renewal date" value={Date.utc_today()} clearable />
<.date_picker name="blackout[]" label="Blackout dates" multiple clearable />
<.date_picker name="ship_date" label="Ship date" typeable clearable />

Error state

.heex
<.date_picker
  name="deadline"
  label="Deadline"
  errors={["Please pick a date in the future"]}
/>

Leading icon

Use :inner_prefix to place an icon inside the toggle border.

.heex
<.date_picker name="leading_icon_date" placeholder="Select a date">
  <:inner_prefix>
    <.icon name="hero-calendar" class="icon" />
  </:inner_prefix>
</.date_picker>

Outer prefix label

:outer_prefix renders content in an attached cell, sharing the field's border.

Due
.heex
<.date_picker name="due" placeholder="Select a date">
  <:outer_prefix class="px-3 text-foreground-soft">Due</:outer_prefix>
</.date_picker>

Outer suffix action

Pair the field with a button or other action through :outer_suffix.

.heex
<.date_picker name="filter_date" placeholder="Filter by date">
  <:outer_suffix>
    <.button variant="outline">Apply</.button>
  </:outer_suffix>
</.date_picker>

Min and max

Constrain the selectable range. Calendar navigation buttons disable past the boundaries.

.heex
<.date_picker
  name="window"
  min={Date.utc_today()}
  max={Date.add(Date.utc_today(), 30)}
/>

Weekdays only

Use the :weekends shortcut to disable Saturdays and Sundays.

.heex
<.date_picker name="business_day" disabled_dates={[:weekends]} />

Holiday calendar

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

.heex
<.date_picker
  name="delivery_date"
  disabled_dates={[
    ~D[2026-12-24],
    ~D[2026-12-31],
    {:month_day, 12, 25},
    {:month_day, 1, 1}
  ]}
/>

Recurring weekday pattern

Disable a specific weekday with {:weekday, n}, where 1 is Monday and 7 is Sunday.

Closed on Mondays
.heex
<.date_picker
  name="clinic_visit"
  disabled_dates={[{:weekday, 1}]}
/>

US display format

.heex
<.date_picker name="date" value={Date.utc_today()} display_format="%m/%d/%Y" />

European display format

.heex
<.date_picker name="date" value={Date.utc_today()} display_format="%d.%m.%Y" />

Long display format

.heex
<.date_picker name="date" value={Date.utc_today()} display_format="%A, %B %-d, %Y" />

Multiple selection

Set multiple to toggle dates on and off. The toggle reads "X dates selected" once two or more are picked.

.heex
<.date_picker
  name="holidays[]"
  label="Company holidays"
  multiple
  placeholder="Select holidays"
/>

Multiple within a range

Combine multiple with min/max for fixed-window selection like a fiscal year.

.heex
<.date_picker
  name="pto[]"
  multiple
  min={~D[2026-01-01]}
  max={~D[2026-12-31]}
/>

Typeable input

Replaces the toggle with a text input. Separators are inserted automatically while typing.

.heex
<.date_picker name="typed_date" label="Date" typeable />

Typeable with custom input format

Override the input mask with input_format. Any combination of dd, mm, and yyyy with a single-character separator is valid.

.heex
<.date_picker
  name="dob"
  typeable
  input_format="dd/mm/yyyy"
/>

Month granularity

Renders a 3-column month grid. Selected values normalize to the first of the month.

.heex
<.date_picker name="report_month" granularity="month" />

Year granularity

Year-only mode for fiscal years and birth years. Values normalize to January 1st.

.heex
<.date_picker name="grad_year" granularity="year" />

Adds previous/next year arrows alongside the month arrows for faster traversal.

.heex
<.date_picker name="date" navigation="extended" />

Multi-month layout

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

.heex
<.date_picker name="multi_month_date" label="Event date" months={2} />

Month and year dropdowns next to the arrows. The classic birth-date pattern.

.heex
<.date_picker
  name="birth_date"
  navigation="select"
  min={~D[1900-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_picker name="date" week_start={1} />

Confirm mode

Adds Cancel/Apply buttons that commit the selection only on Apply.

.heex
<.date_picker name="date" close="confirm" />

Stay open after selection

close="manual" keeps the dropdown open after a date is picked so adjacent controls stay reachable.

.heex
<.date_picker name="date" close="manual" />

Preset shortcuts

Render a sidebar of one-click jumps next to the calendar. The active preset highlights when it matches the selection.

.heex
<.date_picker
  name="reminder"
  presets={[
    {"Today", Date.utc_today()},
    {"Tomorrow", Date.add(Date.utc_today(), 1)},
    {"In a week", Date.add(Date.utc_today(), 7)},
    {"In a month", Date.shift(Date.utc_today(), month: 1)}
  ]}
/>

Spanish locale

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

.heex
<.date_picker
  name="fecha"
  label="Fecha"
  placeholder="Selecciona una fecha"
  locale="es"
/>

German locale

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

.heex
<.date_picker
  name="datum"
  label="Datum"
  locale="de"
  navigation="select"
/>

Japanese locale

.heex
<.date_picker
  name="date_ja"
  label="日付"
  placeholder="日付を選択"
  locale="ja"
/>

Form integration

Changes: %{}
.heex
<.form :let={f} for={@form} phx-change="validate">
  <.date_picker field={f[:date]} />
</.form>
.ex
def handle_event("validate", %{"data" => _params}, socket) do
  # Re-run validation and reassign the form
  {:noreply, socket}
end

Linked range with a number input

Two date pickers and a number_input bound to the same form, kept in sync from the handler. Picking the departure auto-advances the return picker; changing any of the three recomputes the others. A backdrop overlay covers the form while the server is processing.

Aug 1 → Aug 4 · 3 nights
.heex
<div class="relative group space-y-4">
  <.form :let={f} for={@form} as={:trip} phx-change="update" class="grid grid-cols-3 gap-4">
    <.date_picker id="depart" field={f[:depart]} label="Depart" />
    <.date_picker id="return" field={f[:return]} label="Return" />
    <.number_input field={f[:nights]} label="Nights" min={1} max={365} />
  </.form>

  <div class="flex gap-2">
    <.button size="sm" variant="outline" phx-click="preset" phx-value-key="weekend">
      This weekend
    </.button>

    <.button
      size="sm"
      variant="ghost"
      phx-click={
        JS.push("reset")
        |> Fluxon.close_date_picker("depart")
        |> Fluxon.close_date_picker("return")
      }
    >
      Reset
    </.button>
  </div>

  <div
    aria-hidden="true"
    class={[
      "absolute inset-0 z-20 flex items-center justify-center rounded-base",
      "bg-overlay/60 backdrop-blur-xs",
      "opacity-0 pointer-events-none transition-opacity duration-150",
      "group-has-[.phx-click-loading]:opacity-100 group-has-[.phx-click-loading]:pointer-events-auto",
      "group-has-[.phx-change-loading]:opacity-100 group-has-[.phx-change-loading]:pointer-events-auto"
    ]}
  >
    <.loading variant="ring" class="size-6 text-foreground" label="Updating" />
  </div>
</div>
.ex
def handle_event("update", %{"_target" => target, "trip" => params}, socket) do
  depart = parse_date(params["depart"])
  return = parse_date(params["return"])
  nights = parse_int(params["nights"])

  {depart, return, nights} = recompute(target, depart, return, nights)

  socket = assign_trip(socket, depart, return, nights)

  socket =
    if target == ["trip", "depart"] do
      socket
      |> Fluxon.close_date_picker("depart")
      |> Fluxon.open_date_picker("return")
    else
      socket
    end

  {:noreply, socket}
end

def handle_event("preset", %{"key" => "weekend"}, socket) do
  today = Date.utc_today()
  friday = next_weekday(today, 5)
  sunday = Date.add(friday, 2)

  {:noreply,
   socket
   |> assign_trip(friday, sunday, 2)
   |> Fluxon.open_date_picker("return")}
end

defp recompute(["trip", "depart"], depart, return, nights) do
  return = if Date.compare(return, depart) != :gt, do: Date.add(depart, max(nights, 1)), else: return
  {depart, return, Date.diff(return, depart)}
end

defp recompute(["trip", "return"], depart, return, _nights) do
  return = if Date.compare(return, depart) != :gt, do: Date.add(depart, 1), else: return
  {depart, return, Date.diff(return, depart)}
end

defp recompute(["trip", "nights"], depart, _return, nights) do
  nights = nights |> max(1) |> min(365)
  {depart, Date.add(depart, nights), nights}
end