Fluxon.Components.DatePicker (Fluxon v1.0.20)

A powerful and flexible date picker component that provides a rich set of features for date selection. Built with LiveView integration in mind, this component offers comprehensive date handling capabilities from simple single date selection to complex date ranges with time picking.

Usage

At its simplest form, the date picker can be used standalone with just a name attribute:

<.date_picker name="appointment" label="Appointment Date" />

This renders a field button that, when clicked, opens a calendar overlay positioned below the button. The calendar starts at the current month and allows selecting any date. When a date is selected, the calendar closes automatically and the selected date is displayed in the button using the default format (e.g., "Jan 15, 2024").

Like all other form components, the date picker supports various labeling options to provide context and guidance to users:

<.date_picker
  name="appointment"
  label="Appointment Date"
  sublabel="Required"
  description="Choose when you'd like to schedule your consultation"
  help_text="Our office is open Monday through Friday, 9 AM to 5 PM"
/>

Date Range Constraints

The date picker supports limiting the selectable dates through the min and max attributes. Both attributes expect an Elixir Date struct and provide client-side validation only. For security purposes, you should always implement corresponding backend validations, typically through Ecto changesets.

Here's an example that limits date selection to the next 30 days:

<.date_picker
  field={f[:deadline]}
  label="Project Deadline"
  min={Date.utc_today()}
  max={Date.add(Date.utc_today(), 30)}
/>

You can also use date literals with the ~D sigil for fixed dates:

<.date_picker
  name="birth_date"
  label="Birth Date"
  min={~D[1900-01-01]}
  max={Date.utc_today()}
/>

When date constraints are set, the component:

  • Disables and visually styles dates outside the allowed range
  • Prevents keyboard navigation to disabled dates
  • Automatically disables month/year navigation buttons when the visible month would not show any selectable dates
  • Ensures the calendar opens to a month containing selectable dates, even if the current month is outside the allowed range
  • Maintains the constraints when switching between months or years
  • Clears the selection if a previously selected date becomes invalid due to dynamically changed constraints

Multiple Date Selection

The date picker supports selecting multiple dates through the multiple attribute. This mode is particularly useful for scenarios like scheduling recurring events, selecting holiday dates, or planning multiple appointments:

<.date_picker
  field={f[:holidays]}
  label="Company Holidays"
  multiple
  description="Select all company holidays for the year"
  help_text="Click dates to toggle selection"
/>

When multiple date selection is enabled:

  • The calendar remains open after each selection to facilitate choosing multiple dates
  • Each selected date is visually highlighted in the calendar
  • Clicking a selected date deselects it
  • The toggle button displays "X dates selected" when more than one date is chosen
  • Time picker integration is not supported (see Time Picker Integration)
  • Auto-close mode is not supported (see Closing Strategies)

Browser Behavior with Multiple Fields

When using multiple selection fields in HTML forms (e.g., name="dates[]"), browsers have a specific behavior that can affect form submissions:

  • When options are selected, each value is included in the form data
  • However, when all options are deselected, the browser omits the field entirely from the form submission
  • This means the server cannot distinguish between "no form submitted" and "user deselected all dates"

To work around this browser limitation, the component automatically includes a hidden input with an empty value. As a result, when all dates are deselected, an empty string is sent to the backend instead of omitting the field entirely. This results in consistent form submissions:

# When dates are selected
%{
  "date_form" => %{
    "holidays" => ["2025-05-15", "2025-05-14", "2025-05-22"]
  }
}

# When all dates are deselected (hidden input provides the empty value)
%{
  "date_form" => %{
    "holidays" => [""]  # Instead of field being omitted entirely
  }
}

Date Formatting

The date picker uses Elixir's strftime under the hood to format dates. Control how dates are displayed using the display_format attribute:

<.date_picker
  field={f[:date]}
  label="Date"
  display_format="%B %-d, %Y"  # Displays as "January 1, 2024"
/>

The format string should match the type of value being used. When using a date picker without time selection, only date-related format specifiers are valid (e.g., %Y, %m, %d).

Common date format patterns:

  • "%Y-%m-%d": ISO format (2024-01-01)
  • "%b %-d, %Y": Short month (Jan 1, 2024)
  • "%B %-d, %Y": Full month (January 1, 2024)
  • "%d/%m/%Y": European format (01/01/2024)
  • "%m/%d/%Y": US format (01/01/2024)

When using the time picker, you can include time format patterns:

<.date_picker
  field={f[:appointment]}
  label="Appointment"
  show_time_picker
  display_format="%B %-d, %Y at %I:%M %p"  # January 1, 2024 at 02:30 PM
/>

Common datetime format patterns:

  • "%Y-%m-%d %H:%M:%S": ISO format with 24h time (2024-01-01 14:30:00)
  • "%B %-d, %Y at %I:%M %p": Full month with 12h time (January 1, 2024 at 02:30 PM)
  • "%d/%m/%Y %H:%M": European format with 24h time (01/01/2024 14:30)
  • "%m/%d/%Y %I:%M %p": US format with 12h time (01/01/2024 02:30 PM)
  • "%a, %b %-d at %I:%M %p": Short format (Mon, Jan 1 at 02:30 PM)

Format Specifier Compatibility

Time-related format specifiers (%H, %M, %S, %I, %p) are only valid when show_time_picker={true}. Using these specifiers without enabling the time picker will result in formatting errors.

Week Start Configuration

The date picker allows customizing which day of the week appears in the first column through the week_start attribute. This is particularly useful for adapting to different cultural preferences, as some regions start their weeks on Sunday while others prefer Monday:

<.date_picker
  field={f[:date]}
  label="Date"
  week_start={1}  # Week starts on Monday
/>

The week_start attribute accepts a number from 0 to 6, where each number maps to a day:

IntegerWeekdayNotes
0SundayDefault
1MondayCommon in Europe
2Tuesday
3Wednesday
4Thursday
5FridayCommon in Islamic countries
6SaturdayCommon in Nepal

When you change the week start day:

  • The calendar grid adjusts to show the specified day in the first column
  • All other days shift accordingly while maintaining their order
  • Week-based keyboard navigation (e.g., up/down arrows) adapts to the new week structure
  • The calendar maintains the new week start even when navigating between months

Time Picker Integration

The date picker can be enhanced with time selection capabilities through the show_time_picker attribute. This integration changes how the component handles dates and form submissions:

<.date_picker
  field={f[:appointment]}
  label="Appointment"
  show_time_picker
  time_format="12"  # or "24" for 24-hour format
/>

Field Type Requirement

When using the time picker, the field must be a NaiveDateTime or DateTime struct. Using a Date struct will result in runtime exceptions.

The time picker changes how values are handled:

  • Form submissions include time information in ISO 8601 format:
    %{
      "appointment_form" => %{
        "scheduled_appointment" => "2025-02-12 08:00:00"  # Instead of just "2025-02-12"
      }
    }
  • By default, times are displayed in 12-hour format with AM/PM selection
  • Use time_format="24" to switch to 24-hour format
  • Time picker is not available in multiple date selection mode
  • For better UX, the component automatically switches to manual close mode (see Closing Strategies)

Features and behavior:

  • Separate inputs for hours and minutes
  • Arrow keys (↑/↓) increment/decrement values
  • Direct number input for quick value entry
  • AM/PM toggle in 12-hour mode
  • Keyboard navigation between time inputs
  • Values are preserved when switching months/years
  • Time inputs are disabled until a date is selected
  • Enter key confirms the current selection

Date Range Selection

The date picker supports selecting a range of dates through the range attribute. This mode requires specifying both start and end fields, either through form integration or standalone attributes:

Field Requirements

When using range mode, you must provide both start and end fields. Using only one field or using the single field attribute will result in runtime errors.

<.form :let={f} for={@changeset}>
  <.date_picker
    range
    start_field={f[:start_date]}
    end_field={f[:end_date]}
    label="Date Range"
  />
</.form>

Standalone Usage

<.date_picker
  range
  start_name="start_date"
  end_name="end_date"
  start_value={@start_date}
  end_value={@end_date}
  label="Date Range"
/>

When range selection is enabled:

  • The calendar remains open after selecting the first date
  • Visual feedback shows the selected range:
    • Start and end dates are highlighted
    • Days between start and end are styled differently
  • Dates are automatically sorted (earlier date becomes start)
  • Clicking a selected date clears the selection
  • Form submissions include two separate fields:
    %{
      "date_form" => %{
        "start_date" => "2024-03-01",
        "end_date" => "2024-03-15"
      }
    }

The component supports several interaction patterns:

  • Click two different dates to select a range
  • Click the same date twice to create a single-day range
  • Click a date within an existing range to shrink the range
  • Click outside an existing range to expand it
  • Click a selected date to clear the selection and start over

Closing Strategies

The date picker provides three distinct modes for handling when the calendar closes and when changes are confirmed, controlled through the close attribute:

<.date_picker
  field={f[:date]}
  close="auto"     # Default: closes on selection
  # close="manual"   # Stays open after selection
  # close="confirm"  # Requires explicit confirmation
/>

Auto Mode (Default)

The default "auto" mode provides the most streamlined experience for single date selection:

  • Calendar closes immediately after selecting a date
  • Change events are emitted as soon as a date is selected
  • Best suited for simple date picking scenarios
  • Automatically falls back to manual mode when using:
    • Time picker integration
    • Range selection
    • Multiple date selection

Manual Mode

The "manual" mode keeps the calendar open after selection, offering more flexibility:

  • Calendar stays open after date selection
  • Change events are emitted immediately as selections change
  • Users must explicitly close the calendar via:
    • Escape key
    • Clicking outside
    • Clicking the toggle

Confirm Mode

The "confirm" mode adds an explicit confirmation step:

  • Adds Cancel and Confirm buttons at the bottom
  • Calendar stays open until explicitly confirmed or cancelled
  • Changes are treated as "pending" until confirmed
  • Change events are only emitted after clicking the confirm button
  • Cancelling or closing the calendar restores the previous selection

Mode Selection Tips

  • Use "auto" for simple, single date selections
  • Use "manual" when users need to compare or review dates
  • Use "confirm" when accuracy is crucial and changes need explicit confirmation
  • The component will automatically adjust to "manual" mode when using time picker, range selection, or multiple selection features for better user experience

Navigation Modes

The date picker provides three navigation modes that determine how users can move between months and years. Each mode is configured through the navigation attribute.

Default Mode

The default mode offers a simple and clean interface focused on month-to-month navigation. It displays the current month and year as text, with arrow buttons on either side for moving to the previous or next month.

<.date_picker field={f[:date]} navigation="default" />

Extended Mode

Extended mode enhances the default navigation by adding year navigation arrows alongside the month arrows. This creates a more efficient way to navigate through larger time spans while maintaining the familiar arrow-based interaction.

<.date_picker field={f[:date]} navigation="extended" />

Select Mode

Select mode transforms the navigation interface by combining month arrows with dropdown menus for both month and year selection. The dropdowns provide direct access to any month or year within the allowed range.

<.date_picker field={f[:date]} navigation="select" />

Navigation and Constraints

The date picker intelligently handles navigation when date constraints are set:

  • Navigation buttons and dropdowns automatically disable when reaching min/max limits
  • The calendar ensures the visible month always contains selectable dates
  • Year dropdown options are limited to the years within the allowed range

Form Integration

The date picker component provides robust form integration through two approaches: using Phoenix form fields (recommended) or using standalone name attributes. Each approach supports single dates, multiple dates, and date ranges.

Use the field attribute to bind the date picker to a form field:

<.form :let={f} for={@changeset} phx-change="validate" phx-submit="save">
  <.date_picker
    field={f[:appointment_date]}
    label="Appointment Date"
    min={Date.utc_today()}
    max={Date.add(Date.utc_today(), 30)}
  />
</.form>

Using the field attribute provides:

  • Automatic value handling from form data
  • Error handling and validation messages
  • Form submission with correct field names
  • Integration with changesets
  • ID generation for accessibility
  • Proper type conversion between form data and Elixir date types
  • Nested form data handling

Here's a complete example showing different date field types and validations:

defmodule MyApp.Booking do
  use Ecto.Schema
  import Ecto.Changeset

  schema "bookings" do
    # Single date field
    field :appointment_date, :date

    # Date with time
    field :meeting_datetime, :naive_datetime

    # Multiple dates
    field :blocked_dates, {:array, :date}

    # Date range
    field :start_date, :date
    field :end_date, :date

    timestamps()
  end

  def changeset(booking, attrs) do
    booking
    |> cast(attrs, [:appointment_date, :meeting_datetime, :blocked_dates, :start_date, :end_date])
    |> validate_required([:appointment_date])
    |> validate_future_date(:appointment_date)
    |> validate_date_range()
  end

  # Custom validation for future dates
  defp validate_future_date(changeset, field) do
    validate_change(changeset, field, fn _, date ->
      if Date.compare(date, Date.utc_today()) == :lt do
        [{field, "must be in the future"}]
      else
        []
      end
    end)
  end

  # Validate that end_date comes after start_date
  defp validate_date_range(changeset) do
    case {get_field(changeset, :start_date), get_field(changeset, :end_date)} do
      {start_date, end_date} when not is_nil(start_date) and not is_nil(end_date) ->
        if Date.compare(end_date, start_date) == :lt do
          add_error(changeset, :end_date, "must be after start date")
        else
          changeset
        end

      _ ->
        changeset
    end
  end
end

# In your LiveView
def mount(_params, _session, socket) do
  changeset = Booking.changeset(%Booking{}, %{})
  {:ok, assign(socket, form: to_form(changeset))}
end

def render(assigns) do
  ~H"""
  <.form :let={f} for={@form} phx-change="validate">
    <.date_picker
      field={f[:appointment_date]}
      label="Appointment Date"
      min={Date.utc_today()}
    />

    <.date_picker
      field={f[:meeting_datetime]}
      label="Meeting Time"
      show_time_picker
      time_format="12"
    />

    <.date_picker
      field={f[:blocked_dates]}
      label="Blocked Dates"
      multiple
      help_text="Select multiple dates that should be unavailable"
    />

    <.date_picker
      range
      start_field={f[:start_date]}
      end_field={f[:end_date]}
      label="Booking Period"
    />
  </.form>
  """
end

def handle_event("validate", %{"booking" => params}, socket) do
  changeset =
    %Booking{}
    |> Booking.changeset(params)
    |> Map.put(:action, :validate)

  {:noreply, assign(socket, form: to_form(changeset))}
end

Using Standalone Date Pickers

For simpler cases or when not using Phoenix forms, use the name attribute:

# Single date
<.date_picker
  name="filter_date"
  label="Filter By Date"
  value={@selected_date}
/>

# Multiple dates
<.date_picker
  name="holiday_dates[]"
  label="Holiday Dates"
  multiple
  value={@selected_dates}
/>

# Date range
<.date_picker
  range
  start_name="check_in"
  end_name="check_out"
  start_value={@check_in}
  end_value={@check_out}
  label="Stay Period"
/>

# Date with time
<.date_picker
  name="event_time"
  label="Event Time"
  show_time_picker
  time_format="24"
  value={@event_datetime}
/>

When using standalone date pickers:

  • The name attribute determines the form field name
  • Values are managed through the value attribute (or start_value/end_value for ranges)
  • For multiple selection, append [] to the name attribute
  • Errors are passed via the errors attribute
  • Values are submitted in ISO 8601 format (YYYY-MM-DD or YYYY-MM-DD HH:mm:ss)
  • For date ranges, use start_name and end_name to specify field names

Type Handling

The date picker automatically handles conversion between string values and Elixir date types:

  • :date fields expect and return Date structs
  • :naive_datetime fields expect and return NaiveDateTime structs
  • :datetime fields expect and return DateTime structs
  • Multiple selection fields work with lists of these types
  • When using standalone mode, values are always in ISO 8601 string format

Keyboard Support

The component provides comprehensive keyboard navigation:

KeyElement FocusDescription
Tab/Shift+TabToggle buttonMoves focus to and from the date picker
Space/EnterToggle buttonOpens/closes the date picker
CalendarMoves to the same day in the previous week
CalendarMoves to the same day in the next week
CalendarMoves to the previous day
CalendarMoves to the next day
HomeCalendarMoves to the first day of the current week
EndCalendarMoves to the last day of the current week
PageUpCalendarMoves to the same day in the previous month
PageDownCalendarMoves to the same day in the next month
Enter/SpaceCalendarSelects the focused date
EscapeAnyCloses the date picker
BackspaceToggle buttonClears selection (when clearable={true})

When time picker is enabled:

KeyElement FocusDescription
/Time inputIncrements/decrements the value
0-9Time inputSets the value directly
a/pAM/PM selectSwitches between AM/PM

The component implements a focus trap when open, ensuring keyboard navigation stays within the calendar.

Summary

Components

Renders a date picker component with rich features and full keyboard navigation support.

Components

date_picker(assigns)

Renders a date picker component with rich features and full keyboard navigation support.

This component provides a flexible way to handle date selection, from simple single date picking to complex date ranges with time selection. It includes built-in form integration, error handling, and accessibility features.

Attributes

  • id (:any) - The unique identifier for the date picker component. When not provided, a random ID will be generated. Defaults to nil.

  • field (Phoenix.HTML.FormField) - The form field to bind to. When provided, the component automatically handles value tracking, errors, and form submission.

  • name (:any) - The form name for the date picker. Required when not using the field attribute. For multiple selections, this will be automatically suffixed with [].

  • value (:any) - The current selected value(s). For multiple selections, this should be a list. When using forms, this is automatically handled by the field attribute.

  • range (:boolean) - When true, enables date range selection. This requires using either start_field/end_field or start_name/end_name attributes.

    Defaults to false.

  • start_field (Phoenix.HTML.FormField) - The form field for the start date when using range selection with forms. Required when range={true} and using form integration.

  • end_field (Phoenix.HTML.FormField) - The form field for the end date when using range selection with forms. Required when range={true} and using form integration.

  • start_name (:any) - The form name for the start date when using range selection without forms. Required when range={true} and not using form integration.

  • end_name (:any) - The form name for the end date when using range selection without forms. Required when range={true} and not using form integration.

  • start_value (:any) - The current start date value when using range selection without forms.

  • end_value (:any) - The current end date value when using range selection without forms.

  • min (Date) - The earliest date that can be selected. Dates before this will be disabled. Defaults to nil.

  • max (Date) - The latest date that can be selected. Dates after this will be disabled. Defaults to nil.

  • multiple (:boolean) - When true, allows selecting multiple dates. This submits an array of dates. Defaults to false.

  • display_format (:string) - The format string used to display the selected date(s) in the toggle button. Uses strftime format. Common patterns:

    • "%Y-%m-%d": ISO format (2024-01-01)
    • "%b %-d, %Y": Short month (Jan 1, 2024)
    • "%B %-d, %Y": Full month (January 1, 2024)
    • "%d/%m/%Y": European format (01/01/2024)
    • "%m/%d/%Y": US format (01/01/2024)

    Defaults to "%b %-d, %Y".

  • week_start (:integer) - The day of the week that should appear in the first column.

    • 0: Sunday (default)
    • 1: Monday
    • 2: Tuesday
    • 3: Wednesday
    • 4: Thursday
    • 5: Friday
    • 6: Saturday

    Defaults to 0.

  • size (:string) - Controls the size of the date picker component:

    • "sm": Small size, suitable for compact UIs
    • "base": Default size, suitable for most use cases
    • "lg": Large size, suitable for prominent selections
    • "xl": Extra large size, suitable for hero sections

    Defaults to "base".

  • class (:any) - Additional CSS classes to be applied to the date picker calendar container. These classes will be merged with the default styles.

    Defaults to nil.

  • show_time_picker (:boolean) - When true, adds time selection fields below the calendar. This changes the value type from Date to NaiveDateTime in form submissions.

    Defaults to false.

  • time_format (:string) - The time format to use when show_time_picker is true:

    • "12": 12-hour format with AM/PM selection
    • "24": 24-hour format

    Defaults to "12".

  • close (:string) - Controls how the date picker closes after selection:

    • "auto": Closes immediately after selection (default)
    • "manual": Stays open after selection
    • "confirm": Requires explicit confirmation via button

    Defaults to "auto".

  • navigation (:string) - Controls the calendar navigation interface:

    • "default": Month arrows only
    • "extended": Month and year arrows
    • "select": Month arrows + year/month dropdowns

    Defaults to "default".

  • inline (:boolean) - When true, renders the calendar inline instead of in a dropdown. Defaults to false.

  • disabled (:boolean) - When true, disables the date picker component. Disabled date pickers cannot be interacted with and appear visually muted.

    Defaults to false.

  • label (:string) - The primary label for the date picker. This text is displayed above the date picker and is used for accessibility purposes.

    Defaults to nil.

  • sublabel (:string) - Additional context displayed to the side of the main label. Useful for providing extra information without cluttering the main label.

    Defaults to nil.

  • description (:string) - A longer description to provide more context about the date picker. This appears below the label but above the date picker element.

    Defaults to nil.

  • help_text (:string) - Help text to display below the date picker. This can provide additional context or instructions for using the date picker.

    Defaults to nil.

  • placeholder (:string) - Text to display when no date is selected. This text appears in the date picker toggle and helps guide users to make a selection.

    Defaults to nil.

  • errors (:list) - List of error messages to display below the date picker. These are automatically handled when using the field attribute with form validation.

    Defaults to [].