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:
| Feature | Calendar | DatePicker |
|---|---|---|
| Display | Always inline | Dropdown on click |
| Toggle | None | Button or typeable input |
| Time picker | Not supported | Supported |
| Display format | Not applicable | Configurable |
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" />| Granularity | Grid | Navigation Step | Value Format | Use Case |
|---|---|---|---|---|
day | 7-column, 6 weeks | Month | 2025-01-15 | Appointment dates, deadlines |
month | 3-column, 12 months | Year | 2025-03 | Credit card expiry, billing periods |
year | 3-column, 12 years | Decade | 2025 | Fiscal 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" />| Style | Controls | Use Case |
|---|---|---|
default | Previous/next month arrows with month-year title | Most calendars where dates are near the current month |
extended | Adds previous/next year arrows | Booking systems spanning multiple years |
select | Month and year dropdown selects with next/previous arrows | Birth 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:
| Pattern | Example | Description |
|---|---|---|
Date | ~D[2025-01-15] | Specific date |
Date.Range | Date.range(~D[2025-01-01], ~D[2025-01-10]) | Contiguous date range |
:weekends | :weekends | Saturdays and Sundays |
:weekdays | :weekdays | Monday 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:
| Key | Day Mode | Month Mode | Year Mode |
|---|---|---|---|
Arrow Left | Previous day | Previous month | Previous year |
Arrow Right | Next day | Next month | Next year |
Arrow Up | Previous week (7 days) | Up 3 months | Up 3 years |
Arrow Down | Next week (7 days) | Down 3 months | Down 3 years |
Enter / Space | Select focused date | Select focused month | Select focused year |
Tab | Move focus out of calendar | Move focus out of calendar | Move 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
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, thenameandvalueattributes 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 thefieldattribute. For multiple selection, append[]to the name (e.g.,"dates[]").value(:any) - Sets the currently selected date value. AcceptsDate,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 usingfield) or thenameattribute. The id is used to generate sub-element ids like{id}-containerand{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. AcceptsDate,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. AcceptsDate,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 totrue. 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 thefieldattribute, 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
[].- Specific dates:
allow_deselect(:boolean) - Controls whether clicking an already-selected date deselects it. Defaults totrue.- 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
falsewhen 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 includingar,de,es,fr,ja,ko,pt-BR,zh, and others.Defaults to
"en".static(:boolean) - Renders a non-interactive, display-only calendar when set totrue. Removes the navigation header, hidden form inputs, and the JavaScript hook. All day buttons are disabled withtabindex="-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 totrue. Each click toggles a date on or off. A separate hidden input is created for each selected date, so thenameattribute should end with[](e.g.,"dates[]"). Defaults tofalse.Defaults to
false.
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_nameandstart_valueare 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_nameandend_valueare inferred from the field. Must be used together withstart_field.start_name(:any) - Sets the form input name for the start date. Required when not using thestart_fieldattribute. 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 theend_fieldattribute. The hidden input for the end date uses this name for form submission.start_value(:any) - Sets the initial start date value. AcceptsDate,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. AcceptsDate,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 usingfield) or thenameattribute. The id is used to generate sub-element ids like{id}-containerand{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. AcceptsDate,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. AcceptsDate,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 totrue. 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 thefieldattribute, 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
[].- Specific dates:
allow_deselect(:boolean) - Controls whether clicking an already-selected date deselects it. Defaults totrue.- 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
falsewhen 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 includingar,de,es,fr,ja,ko,pt-BR,zh, and others.Defaults to
"en".static(:boolean) - Renders a non-interactive, display-only calendar when set totrue. Removes the navigation header, hidden form inputs, and the JavaScript hook. All day buttons are disabled withtabindex="-1". Selected dates remain visually highlighted. Useful for confirmation screens, read-only summaries, or print layouts.Defaults to
false.