Fluxon.Components.Calendar (Fluxon v3.0.0)

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 day, month, and year granularity modes, multiple navigation styles, min/max constraints, flexible disabled date patterns, locale support, and roving keyboard navigation.

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 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.

Focus moves through the grid with a roving tabindex, so only one cell is tabbable at a time and Tab enters or leaves the grid as a single stop. Arrow keys move focus and wrap across grid edges, advancing to the previous or next period automatically: left and right step by one unit (a day, month, or year), while up and down step by a full row, which is a week in day view and three cells in month and year views. Enter or Space selects the focused cell. Focus skips disabled cells, continuing in the arrow direction until it lands on a selectable one.

Navigation

The navigation attribute controls 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.

Time Zones

The calendar is a wall-clock component. It emits and consumes the exact date the user sees, with no timezone attached. Selected dates are never converted across time zones, so what the user picks is what your handle_event receives, and a submitted value is a plain Date you store as-is.

The highlighted "today" needs no configuration: when today is omitted the client highlights the browser's local date, correct for the user's time zone for single-locale and multi-zone apps alike. Set today only when the server must control the value: static mode (no JavaScript runs to apply the highlight), a highlight in the first server paint, deterministic rendering (tests, screenshots), or anchoring to a non-clock date such as a business "today".

Time-zone-aware constraints

min, max, and disabled_dates are wall-clock values, enforced exactly as you pass them. When a bound is relative to the current day (for example "today or later"), compute it in the user's time zone so it matches the day they see. A bound built from Date.utc_today() is the same for everyone, so near midnight it can sit a day ahead of or behind the user's real day and block or allow a date it should not. The usual approach is to send the browser time zone through connect params and resolve the date on the server:

// app.js
let liveSocket = new LiveSocket("/live", Socket, {
  params: {
    _csrf_token: csrfToken,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
  }
})
# In your LiveView mount/3
def mount(_params, _session, socket) do
  timezone = Phoenix.LiveView.get_connect_params(socket)["timezone"] || "Etc/UTC"
  today = DateTime.now!(timezone) |> DateTime.to_date()
  {:ok, assign(socket, today: today)}
end
<.calendar name="date" min={@today} />

If your schema records an absolute moment rather than a date, attach the time and the user's zone on the server before converting to UTC. See Fluxon.Components.DatePicker for the date and time workflow.

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 />

In static mode, the calendar is read-only: every cell is disabled and removed from the tab order, no JavaScript is attached, and no hidden inputs are rendered.

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 accessible 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 include ar, bg, cs, da, de, el, en, es, fi, fr, hi, hr, hu, it, ja, ko, nb, nl, pl, pt, pt-BR, ro, ru, sk, sr, sv, th, tr, uk, vi, and zh.

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.

  • today (:any) - Sets the date treated as "today", which controls the highlighted current day and the default visible month when no value is selected. Accepts Date, DateTime, NaiveDateTime, or ISO 8601 strings, normalized to a Date.

    When omitted, the client highlights the browser's local date, so the current day is correct for the user's time zone with no configuration. Set this to let the server control the value instead: for static mode, a first-paint highlight, deterministic rendering (tests, screenshots), or anchoring to a non-clock date. See the "Time Zones" section for details.

    Defaults to nil.

  • week_start (:integer) - Determines which day of the week appears in the first column of the day grid. When not provided, the value is derived from the component's locale.

    • 0: Sunday (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 nil.

  • 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". Must be one of "day", "month", or "year".

  • hide_outside_days (:boolean) - Controls whether the day grid fills the leading and trailing empty cells with days from the previous and next month. Only affects day granularity.

    • false (default): renders a padded six-week grid, with outside-month days dimmed. The grid keeps a constant height across months.
    • true: renders only the current month's days. Leading and trailing cells are left blank and full outside-month weeks are dropped, so the grid uses four to six rows depending on the month and its height changes as you navigate.

    Defaults to false.

  • 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". Must be one of "default", "extended", or "select".

  • 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 accessible labels. Defaults to "en". Unsupported locales fall back to English. Common values include ar, de, es, fr, ja, ko, pt-BR, and zh.

    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 and removed from the tab order. 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.

  • today (:any) - Sets the date treated as "today", which controls the highlighted current day and the default visible month when no value is selected. Accepts Date, DateTime, NaiveDateTime, or ISO 8601 strings, normalized to a Date.

    When omitted, the client highlights the browser's local date, so the current day is correct for the user's time zone with no configuration. Set this to let the server control the value instead: for static mode, a first-paint highlight, deterministic rendering (tests, screenshots), or anchoring to a non-clock date. See the "Time Zones" section for details.

    Defaults to nil.

  • week_start (:integer) - Determines which day of the week appears in the first column of the day grid. When not provided, the value is derived from the component's locale.

    • 0: Sunday (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 nil.

  • 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". Must be one of "day", "month", or "year".

  • hide_outside_days (:boolean) - Controls whether the day grid fills the leading and trailing empty cells with days from the previous and next month. Only affects day granularity.

    • false (default): renders a padded six-week grid, with outside-month days dimmed. The grid keeps a constant height across months.
    • true: renders only the current month's days. Leading and trailing cells are left blank and full outside-month weeks are dropped, so the grid uses four to six rows depending on the month and its height changes as you navigate.

    Defaults to false.

  • 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". Must be one of "default", "extended", or "select".

  • 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 accessible labels. Defaults to "en". Unsupported locales fall back to English. Common values include ar, de, es, fr, ja, ko, pt-BR, and zh.

    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 and removed from the tab order. Selected dates remain visually highlighted. Useful for confirmation screens, read-only summaries, or print layouts.

    Defaults to false.