Components Date picker

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Date picker

A calendar in a panel under a real text input. Type 14 Mar 2027 and the grid follows as the year lands; Enter commits and closes; the form posts ISO 8601 either way.
Input plus calendar

The input shows a formatted date you can type into; a hidden input posts ISO 8601 whatever the display format is. Click the calendar icon, or press Escape to close and land back on the input.

heex
<.date_picker
id="showcase-date-picker"
name="due_on"
label="Due date"
value={~D[2026-03-14]}
format="%d %b %Y"
clearable
/>
Two-month range

Range mode posts from and to sub-fields. two_months puts the second pane beside the first, which is what a booking flow wants.

heex
<.date_picker
id="showcase-date-picker-range"
name="stay"
mode="range"
label="Stay"
two_months
format="%d %b"
value={{~D[2026-03-09], ~D[2026-03-17]}}
/>
Field surface

Errors, help text and the required marker come from the same field surface as <.field>, so a picker sits in a form without looking bolted on.

can't be blank

We only take bookings from tomorrow.
heex
<.date_picker
id="showcase-date-picker-errors"
name="starts_on"
label="Start date"
required
placeholder="Pick a start date"
help_text="We only take bookings from tomorrow."
errors={["can't be blank"]}
/>
Properties
Attribute Type Default Description
class any nil extra classes for the wrapper
clear_label string "Clear date" accessible label for the clear button
clearable boolean false show a clear button whenever the field holds text
day_names list nil 7 short day labels ordered Monday-first
day_names_long list nil 7 full day labels ordered Monday-first
disabled boolean false disable the input and the panel
disabled_dates any nil a list of Dates, or a 1-arity function Date -> boolean
errors list [] error messages rendered under the input
field {:struct, Phoenix.HTML.FormField} nil a form field; wires name, value, errors and label like <.field>
fixed_weeks boolean false always render 6 week rows
format string "%Y-%m-%d" strftime format for the display input (Calendar.strftime/2); the posted value is always ISO 8601
help_text string nil context shown under the input
id string the picker id; autogenerated if not set
label string nil the field label; omitted when nil
max any nil latest selectable Date (inclusive)
min any nil earliest selectable Date (inclusive)
mode string "single" selection behaviour
one of: "single", "range"
month any nil the displayed month as any Date within it
month_names list nil 12 month labels
name any nil the posted field name; derived from field when given
on_clear any nil event pushed when the clear button is clicked
on_month_change any nil event pushed by the prev/next month buttons
on_select any nil event pushed when a day is clicked, with the ISO date
open_label string "Choose date" accessible label for the toggle button
panel_class any nil extra classes for the panel
placeholder string nil placeholder for the display input
range_separator string " - " what sits between the two dates in the display input in range mode
required boolean false mark the field required
rest global
show_outside_days boolean true render adjacent-month days in the grid
starts_on integer 1 first day of the week, ISO day numbers (1 = Monday, 7 = Sunday)
one of: 1, 2, 3, 4, 5, 6, 7
target any nil phx-target for the picker's events
today any nil the date treated as today; defaults to Date.utc_today/0
two_months boolean false render two month panes side by side. A range reads naturally across the pair, and single mode earns it too - the flight-search pattern, where seeing next month saves a page before the date you are really after
value any nil a Date, an ISO string, or a {from, to} pair in range mode