Fluxon.Components.Calendar (Fluxon v2.4.0-rc.2)

Provides <.calendar> and <.calendar_range> components for inline date selection.

This module renders a standalone calendar interface that is always visible on the page, supporting single date, multiple date, and date range selection. It includes three granularity modes (day, month, year), three navigation styles, min/max constraints, flexible disabled date patterns, locale support for 30 languages, and full keyboard navigation with accessibility built in.

Calendar vs DatePicker

Both components share the same calendar grid, but serve different layout needs:

FeatureCalendarDatePicker
DisplayAlways inlineDropdown on click
ToggleNoneButton or typeable input
Time pickerNot supportedSupported
Display formatNot applicableConfigurable

Use Calendar when the calendar should always be visible on the page. Use Fluxon.Components.DatePicker when you need a compact input that opens a calendar dropdown.

Usage

Render a calendar for single date selection:

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

For multiple dates or date ranges, use the multiple attribute or the <.calendar_range> component:

<.calendar name="holidays[]" label="Company Holidays" multiple />

<.calendar_range
  start_name="check_in"
  end_name="check_out"
  label="Stay Period"
/>

Selection Modes

Single Selection

The default mode selects one date at a time. Clicking a new date replaces the previous selection. Clicking the selected date deselects it (unless allow_deselect is false):

<.calendar name="date" label="Pick a date" />
<.calendar name="date" label="Required date" allow_deselect={false} />

Multiple Selection

Enable with the multiple attribute. Each click toggles the date on or off. Multiple hidden inputs are created for form submission, so the name attribute should end with []:

<.calendar name="dates[]" label="Select dates" multiple />

When allow_deselect is false, at least one date must remain selected.

Range Selection

Use calendar_range/1 for start/end date pairs. The first click sets the start date, the second click sets the end date. If the end date is before the start, they are automatically swapped. Clicking the start date after a complete range clears both values:

<.calendar_range
  start_name="check_in"
  end_name="check_out"
  label="Booking Period"
/>

Dates between the start and end are visually highlighted with a background fill connecting the two selected endpoints.

Granularity

Controls the precision level of the calendar grid. The granularity attribute determines what unit the user selects and how the grid is laid out:

<.calendar name="date" granularity="day" />
<.calendar name="month" granularity="month" />
<.calendar name="year" granularity="year" />
GranularityGridNavigation StepValue FormatUse Case
day7-column, 6 weeksMonth2025-01-15Appointment dates, deadlines
month3-column, 12 monthsYear2025-03Credit card expiry, billing periods
year3-column, 12 yearsDecade2025Fiscal years, birth years

All selection modes (single, multiple, range) work with every granularity.

Navigation

Three navigation styles control the header UI. Choose based on how much control users need over time traversal:

<.calendar name="date" navigation="default" />
<.calendar name="date" navigation="extended" />
<.calendar name="date" navigation="select" />
StyleControlsUse Case
defaultPrevious/next month arrows with month-year titleMost calendars where dates are near the current month
extendedAdds previous/next year arrowsBooking systems spanning multiple years
selectMonth and year dropdown selects with next/previous arrowsBirth date pickers or historical date selection

Navigation buttons are automatically disabled when they would move past min or max boundaries.

Date Constraints

Min and Max

Restrict the selectable date range. Dates outside the range are visually dimmed, cannot be selected via click or keyboard, and navigation buttons are disabled at boundaries:

<!-- Only future dates -->
<.calendar name="date" min={Date.utc_today()} />

<!-- Fixed year range -->
<.calendar name="date" min={~D[2025-01-01]} max={~D[2025-12-31]} />

Disabled Dates

The disabled_dates attribute accepts a list of patterns to disable specific dates. Multiple patterns can be combined in a single list:

<!-- Disable weekends -->
<.calendar name="date" disabled_dates={[:weekends]} />

<!-- Disable holidays and recurring dates -->
<.calendar name="date" disabled_dates={[~D[2025-12-25], {:month_day, 1, 1}]} />

Available patterns:

PatternExampleDescription
Date~D[2025-01-15]Specific date
Date.RangeDate.range(~D[2025-01-01], ~D[2025-01-10])Contiguous date range
:weekends:weekendsSaturdays and Sundays
:weekdays:weekdaysMonday through Friday
{:day, d}{:day, 15}Day of month, every month
{:weekday, n}{:weekday, 3}Day of week (1=Mon, 7=Sun)
{:week, n}{:week, 33}ISO week number
{:month_day, m, d}{:month_day, 12, 25}Recurring annual date
{:month, m}{:month, 4}Entire month
{:year, y}{:year, 2025}Entire year

Keyboard navigation automatically skips disabled dates, continuing in the arrow key direction until an enabled date is found.

Static Mode

Renders a display-only calendar without navigation controls, hidden inputs, or the JavaScript hook. Selected dates are still visually highlighted. Useful for confirmation screens, read-only summaries, or print layouts:

<.calendar name="date" value={~D[2025-06-15]} static />

All day buttons are rendered as disabled with tabindex="-1" and no phx-hook attribute is applied to the container.

Form Integration

Use the field attribute to bind to a Phoenix form field. The component automatically derives name, value, and errors from the form field:

<.form :let={f} for={@changeset} phx-change="validate">
  <.calendar field={f[:appointment_date]} label="Appointment Date" />
</.form>

Range selection with form fields:

<.form :let={f} for={@changeset}>
  <.calendar_range
    start_field={f[:start_date]}
    end_field={f[:end_date]}
    label="Booking Period"
  />
</.form>

For standalone usage without a form, use the name and value attributes directly:

<.calendar name="date" value={~D[2025-06-15]} />

The component creates hidden <input> elements that participate in standard form submission. In multiple mode, one hidden input is rendered per selected date. In range mode, separate hidden inputs are created for start and end dates.

Locale

The locale attribute localizes weekday abbreviations, month names, header titles, and ARIA labels. Unsupported locales fall back to English:

<.calendar name="fecha" label="Fecha" locale="es" />
<.calendar name="date" label="Date" locale="fr" week_start={1} />
<.calendar name="datum" label="Datum" locale="de" navigation="select" />

Supported locales: ar, bg, cs, da, de, el, en, es, fi, fr, hr, hu, it, ja, ko, nb, nl, pl, pt, pt-BR, ro, ru, sk, sr, sv, th, tr, uk, vi, zh.

Keyboard Navigation

The calendar uses a roving tabindex pattern where only one date cell is tabbable at a time. Arrow keys move focus and automatically navigate months when reaching grid boundaries:

KeyDay ModeMonth ModeYear Mode
Arrow LeftPrevious dayPrevious monthPrevious year
Arrow RightNext dayNext monthNext year
Arrow UpPrevious week (7 days)Up 3 monthsUp 3 years
Arrow DownNext week (7 days)Down 3 monthsDown 3 years
Enter / SpaceSelect focused dateSelect focused monthSelect focused year
TabMove focus out of calendarMove focus out of calendarMove focus out of calendar

When a focused date is disabled, the keyboard continues in the same direction until it finds an enabled date.

Real-World Examples

Business Day Selector

<.calendar
  name="business_date"
  label="Meeting Date"
  min={Date.utc_today()}
  disabled_dates={[:weekends]}
  allow_deselect={false}
/>

Holiday Picker with Validation

<.form :let={f} for={@changeset} phx-change="validate" phx-submit="save">
  <.calendar
    field={f[:holidays]}
    label="Company Holidays"
    multiple
    min={~D[2025-01-01]}
    max={~D[2025-12-31]}
  />
</.form>

Booking Range with Locale

<.calendar_range
  start_name="check_in"
  end_name="check_out"
  label="Periodo de estancia"
  locale="es"
  min={Date.utc_today()}
  week_start={1}
  navigation="extended"
/>

Month/Year Pickers

<.calendar name="birth_month" label="Birth Month" granularity="month" />
<.calendar
  name="fiscal_year"
  label="Fiscal Year"
  granularity="year"
  min={~D[2020-01-01]}
  max={~D[2030-12-31]}
/>

Read-Only Confirmation

<!-- Display selected date on a confirmation page -->
<.calendar name="appointment" value={@appointment_date} static />

Summary

Components

Renders an inline calendar for single or multiple date selection.

Renders an inline calendar for date range selection with separate start and end values.

Components

calendar(assigns)

Renders an inline calendar for single or multiple date selection.

In single mode (default), clicking a date selects it and clicking again deselects it. In multiple mode, each click toggles the date on or off, and a separate hidden input is created for each selected value. The calendar is always visible on the page and supports all granularity modes, navigation styles, and date constraints.

Examples

<.calendar name="date" label="Select Date" />

<.calendar name="dates[]" label="Select Dates" multiple />

<.calendar
  name="appointment"
  label="Appointment"
  min={Date.utc_today()}
  disabled_dates={[:weekends]}
  navigation="extended"
/>

With a form field:

<.form :let={f} for={@changeset} phx-change="validate">
  <.calendar field={f[:date]} label="Date" min={Date.utc_today()} />
</.form>

Attributes

  • field (Phoenix.HTML.FormField) - Binds the calendar to a Phoenix form field for automatic name, value, and error derivation. When provided, the name and value attributes are inferred from the form field. Validation errors display automatically when the field has been used.

  • name (:any) - Sets the form input name for the calendar. Required when not using the field attribute. For multiple selection, append [] to the name (e.g., "dates[]").

  • value (:any) - Sets the currently selected date value. Accepts Date, DateTime, NaiveDateTime, or ISO 8601 date strings. In multiple mode, accepts a list of date values.

  • id (:any) - Sets the unique identifier for the calendar component. When not provided, defaults to the form field id (if using field) or the name attribute. The id is used to generate sub-element ids like {id}-container and {id}-calendar.

    Defaults to nil.

  • min (Date) - Specifies the earliest selectable date. Dates before this boundary are visually dimmed and cannot be selected via click or keyboard. Navigation buttons are disabled when they would move past this date. Accepts Date, DateTime, or ISO 8601 date strings.

    Defaults to nil.

  • max (Date) - Specifies the latest selectable date. Dates after this boundary are visually dimmed and cannot be selected via click or keyboard. Navigation buttons are disabled when they would move past this date. Accepts Date, DateTime, or ISO 8601 date strings.

    Defaults to nil.

  • week_start (:integer) - Determines which day of the week appears in the first column of the day grid.

    • 0 — Sunday (default, common in the US)
    • 1 — Monday (common in Europe and most ISO locales)
    • 6 — Saturday (common in some Middle Eastern locales)

    Values 0-6 map to Sunday through Saturday respectively.

    Defaults to 0.

  • class (:any) - Additional CSS classes applied to the calendar wrapper element. Useful for controlling width, spacing, or custom styling of the calendar panel.

    Defaults to nil.

  • granularity (:string) - Controls the selection precision level and grid layout.

    • day — 7-column grid showing individual days. Navigates by month. Best for appointment dates and deadlines.
    • month — 3-column grid showing 12 months. Navigates by year. Best for billing periods and expiry dates.
    • year — 3-column grid showing 12 years. Navigates by decade. Best for fiscal years and birth years.

    Defaults to "day".

  • navigation (:string) - Controls the navigation interface in the calendar header.

    • default — Previous/next month arrows with a month-year title. Best for most use cases where dates are near the current month.
    • extended — Adds previous/next year arrows for faster traversal. Best for booking systems spanning multiple years.
    • select — Month and year dropdown selects with navigation arrows. Best for birth date pickers or historical date selection.

    Defaults to "default".

  • disabled (:boolean) - Disables the entire calendar when set to true. All date buttons and navigation controls become non-interactive. The calendar remains visible but no selection can be made.

    Defaults to false.

  • label (:string) - Sets the primary label text displayed above the calendar. Renders as a <label> element for accessibility.

    Defaults to nil.

  • sublabel (:string) - Specifies secondary text displayed inline beside the main label. Useful for adding optional context like "(optional)" or a brief clarification.

    Defaults to nil.

  • description (:string) - Provides a longer description rendered as a paragraph below the label. Useful for instructions or additional context about the expected selection.

    Defaults to nil.

  • help_text (:string) - Displays helper text below the calendar grid. Useful for formatting hints, selection guidance, or contextual information.

    Defaults to nil.

  • errors (:list) - Specifies error messages to display below the calendar. When using the field attribute, errors are automatically derived from form validation. Each error renders as a styled error message in red.

    Defaults to [].

  • disabled_dates (:list) - Specifies dates, date ranges, or patterns to disable. Disabled dates are visually dimmed with a strikethrough and cannot be selected via click or keyboard. Multiple patterns can be combined in a single list. Accepts:

    • Specific dates: ~D[2025-01-15]
    • Date ranges: Date.range(~D[2025-01-01], ~D[2025-01-10])
    • Day shortcuts: :weekends, :weekdays
    • Day of month: {:day, 15} (every month)
    • Weekday: {:weekday, 3} (1=Monday, 7=Sunday)
    • ISO week: {:week, 33}
    • Recurring annual dates: {:month_day, 12, 25}
    • Month pattern: {:month, 4} (entire month)
    • Year pattern: {:year, 2025} (entire year)

    Defaults to [].

  • allow_deselect (:boolean) - Controls whether clicking an already-selected date deselects it. Defaults to true.

    • In single mode, clicking the selected date clears the value.
    • In multiple mode, at least one date must remain selected when false.
    • In range mode, clicking the start date of a complete range won't clear both values.

    Set to false when a selection is always required.

    Defaults to true.

  • locale (:string) - Sets the locale for localizing weekday abbreviations, month names, header titles, and ARIA labels. Defaults to "en". Unsupported locales fall back to English. Supports 30 languages including ar, de, es, fr, ja, ko, pt-BR, zh, and others.

    Defaults to "en".

  • static (:boolean) - Renders a non-interactive, display-only calendar when set to true. Removes the navigation header, hidden form inputs, and the JavaScript hook. All day buttons are disabled with tabindex="-1". Selected dates remain visually highlighted. Useful for confirmation screens, read-only summaries, or print layouts.

    Defaults to false.

  • multiple (:boolean) - Enables multiple date selection when set to true. Each click toggles a date on or off. A separate hidden input is created for each selected date, so the name attribute should end with [] (e.g., "dates[]"). Defaults to false.

    Defaults to false.

calendar_range(assigns)

Renders an inline calendar for date range selection with separate start and end values.

The first click sets the start date, the second click sets the end date. If the end date is earlier than the start, the values are automatically swapped. Dates between the endpoints are visually highlighted with a connecting background fill. Clicking the start date of a complete range clears both values (unless allow_deselect is false). Two hidden inputs are created for form submission, one for each endpoint.

Examples

<.calendar_range start_name="check_in" end_name="check_out" label="Stay" />

<.calendar_range
  start_name="start"
  end_name="end"
  label="Project Timeline"
  min={Date.utc_today()}
  navigation="extended"
/>

With form fields:

<.form :let={f} for={@changeset} phx-change="validate">
  <.calendar_range
    start_field={f[:start_date]}
    end_field={f[:end_date]}
    label="Booking Period"
  />
</.form>

Attributes

  • start_field (Phoenix.HTML.FormField) - Binds the range start date to a Phoenix form field. When provided, start_name and start_value are inferred from the field. Validation errors from both start and end fields are collected and displayed together.

  • end_field (Phoenix.HTML.FormField) - Binds the range end date to a Phoenix form field. When provided, end_name and end_value are inferred from the field. Must be used together with start_field.

  • start_name (:any) - Sets the form input name for the start date. Required when not using the start_field attribute. The hidden input for the start date uses this name for form submission.

  • end_name (:any) - Sets the form input name for the end date. Required when not using the end_field attribute. The hidden input for the end date uses this name for form submission.

  • start_value (:any) - Sets the initial start date value. Accepts Date, DateTime, NaiveDateTime, or ISO 8601 date strings. The start date is visually highlighted with a filled circle at the beginning of the range.

  • end_value (:any) - Sets the initial end date value. Accepts Date, DateTime, NaiveDateTime, or ISO 8601 date strings. The end date is visually highlighted with a filled circle at the end of the range.

  • id (:any) - Sets the unique identifier for the calendar component. When not provided, defaults to the form field id (if using field) or the name attribute. The id is used to generate sub-element ids like {id}-container and {id}-calendar.

    Defaults to nil.

  • min (Date) - Specifies the earliest selectable date. Dates before this boundary are visually dimmed and cannot be selected via click or keyboard. Navigation buttons are disabled when they would move past this date. Accepts Date, DateTime, or ISO 8601 date strings.

    Defaults to nil.

  • max (Date) - Specifies the latest selectable date. Dates after this boundary are visually dimmed and cannot be selected via click or keyboard. Navigation buttons are disabled when they would move past this date. Accepts Date, DateTime, or ISO 8601 date strings.

    Defaults to nil.

  • week_start (:integer) - Determines which day of the week appears in the first column of the day grid.

    • 0 — Sunday (default, common in the US)
    • 1 — Monday (common in Europe and most ISO locales)
    • 6 — Saturday (common in some Middle Eastern locales)

    Values 0-6 map to Sunday through Saturday respectively.

    Defaults to 0.

  • class (:any) - Additional CSS classes applied to the calendar wrapper element. Useful for controlling width, spacing, or custom styling of the calendar panel.

    Defaults to nil.

  • granularity (:string) - Controls the selection precision level and grid layout.

    • day — 7-column grid showing individual days. Navigates by month. Best for appointment dates and deadlines.
    • month — 3-column grid showing 12 months. Navigates by year. Best for billing periods and expiry dates.
    • year — 3-column grid showing 12 years. Navigates by decade. Best for fiscal years and birth years.

    Defaults to "day".

  • navigation (:string) - Controls the navigation interface in the calendar header.

    • default — Previous/next month arrows with a month-year title. Best for most use cases where dates are near the current month.
    • extended — Adds previous/next year arrows for faster traversal. Best for booking systems spanning multiple years.
    • select — Month and year dropdown selects with navigation arrows. Best for birth date pickers or historical date selection.

    Defaults to "default".

  • disabled (:boolean) - Disables the entire calendar when set to true. All date buttons and navigation controls become non-interactive. The calendar remains visible but no selection can be made.

    Defaults to false.

  • label (:string) - Sets the primary label text displayed above the calendar. Renders as a <label> element for accessibility.

    Defaults to nil.

  • sublabel (:string) - Specifies secondary text displayed inline beside the main label. Useful for adding optional context like "(optional)" or a brief clarification.

    Defaults to nil.

  • description (:string) - Provides a longer description rendered as a paragraph below the label. Useful for instructions or additional context about the expected selection.

    Defaults to nil.

  • help_text (:string) - Displays helper text below the calendar grid. Useful for formatting hints, selection guidance, or contextual information.

    Defaults to nil.

  • errors (:list) - Specifies error messages to display below the calendar. When using the field attribute, errors are automatically derived from form validation. Each error renders as a styled error message in red.

    Defaults to [].

  • disabled_dates (:list) - Specifies dates, date ranges, or patterns to disable. Disabled dates are visually dimmed with a strikethrough and cannot be selected via click or keyboard. Multiple patterns can be combined in a single list. Accepts:

    • Specific dates: ~D[2025-01-15]
    • Date ranges: Date.range(~D[2025-01-01], ~D[2025-01-10])
    • Day shortcuts: :weekends, :weekdays
    • Day of month: {:day, 15} (every month)
    • Weekday: {:weekday, 3} (1=Monday, 7=Sunday)
    • ISO week: {:week, 33}
    • Recurring annual dates: {:month_day, 12, 25}
    • Month pattern: {:month, 4} (entire month)
    • Year pattern: {:year, 2025} (entire year)

    Defaults to [].

  • allow_deselect (:boolean) - Controls whether clicking an already-selected date deselects it. Defaults to true.

    • In single mode, clicking the selected date clears the value.
    • In multiple mode, at least one date must remain selected when false.
    • In range mode, clicking the start date of a complete range won't clear both values.

    Set to false when a selection is always required.

    Defaults to true.

  • locale (:string) - Sets the locale for localizing weekday abbreviations, month names, header titles, and ARIA labels. Defaults to "en". Unsupported locales fall back to English. Supports 30 languages including ar, de, es, fr, ja, ko, pt-BR, zh, and others.

    Defaults to "en".

  • static (:boolean) - Renders a non-interactive, display-only calendar when set to true. Removes the navigation header, hidden form inputs, and the JavaScript hook. All day buttons are disabled with tabindex="-1". Selected dates remain visually highlighted. Useful for confirmation screens, read-only summaries, or print layouts.

    Defaults to false.