Date picker
UI::DatePickerA typed date field with a calendar, on Cally.
# VelvetKit
## Date picker (`UI::DatePicker`)
A typed date field with a calendar, on Cally.
Add it: `bin/rails generate velvetkit:add date_picker`
Also adds: `label`, `form_field`, `icon`
Stimulus: `ui--date-picker`
Installs: `cally`
### Arguments
```ruby
UI::DatePicker.new(name: nil, value: nil, form: nil, field: nil, label: nil, required: false, error: nil, disabled: false, hint: true, min: nil, max: nil, class: nil, **html_attrs)
```
### Notes
UI::DatePicker — a typed date field with a calendar beside it.
Three inputs in one: what the user types (DD/MM/YYYY), what the form submits
(ISO, hidden), and the calendar. The typed field comes first because people
who know the date type it in a second: reaching a date years away in a
calendar means clicking back month after month.
The written format is not decided here. It comes from `date.formats.typed`
in the locale, and this component derives the placeholder, the parse order
and the separator from that one string — so a locale that writes dates
differently changes what the field accepts, what it shows and what the hint
says, together, with nothing hardcoded.
The shipped English format is day-first; change `date.formats.typed` for
month-first. A native date input follows the browser's locale instead, which
is how the same form reads MM/DD on one machine and DD/MM on another, and how
the 2nd of May gets recorded as the 5th of February.
Cally is ~9 KB gzipped and loaded only when the calendar is first opened, so
a form nobody opens a calendar on downloads nothing.
Usage:
<%= render(UI::DatePicker.new(form: f, field: :starts_on, required: true)) %>
<%= render(UI::DatePicker.new(name: "event[date]", value: @date, label: "Date")) %>
### Example: Default
```erb
<div class="max-w-xs">
<%= render(UI::DatePicker.new(name: "event[date]", label: "Date of birth", value: Date.new(2012, 5, 2))) %>
</div>
```
### Example: States
```erb
<div class="flex max-w-xs flex-col gap-6">
<%= render(UI::DatePicker.new(name: "a", label: "Empty")) %>
<%= render(UI::DatePicker.new(name: "b", label: "Required", required: true)) %>
<%= render(UI::DatePicker.new(name: "c", label: "With an error", error: true, value: Date.new(2026, 10, 21))) %>
<%= render(UI::DatePicker.new(name: "d", label: "Disabled", disabled: true, value: Date.new(2026, 10, 21))) %>
<%= render(UI::DatePicker.new(name: "e", label: "Without the hint", hint: false)) %>
</div>
```
bin/rails generate velvetkit:add date_picker
- Also adds
- Label Form field Icon
- Stimulus
- ui--date-picker
- Installs
- cally
Examples
Default
Type the date as DD/MM/YYYY, or use the calendar
default.html.erb
<div class="max-w-xs">
<%= render(UI::DatePicker.new(name: "event[date]", label: "Date of birth", value: Date.new(2012, 5, 2))) %>
</div>
States
Type the date as DD/MM/YYYY, or use the calendar
Type the date as DD/MM/YYYY, or use the calendar
Type the date as DD/MM/YYYY, or use the calendar
Type the date as DD/MM/YYYY, or use the calendar
states.html.erb
<div class="flex max-w-xs flex-col gap-6">
<%= render(UI::DatePicker.new(name: "a", label: "Empty")) %>
<%= render(UI::DatePicker.new(name: "b", label: "Required", required: true)) %>
<%= render(UI::DatePicker.new(name: "c", label: "With an error", error: true, value: Date.new(2026, 10, 21))) %>
<%= render(UI::DatePicker.new(name: "d", label: "Disabled", disabled: true, value: Date.new(2026, 10, 21))) %>
<%= render(UI::DatePicker.new(name: "e", label: "Without the hint", hint: false)) %>
</div>
API
Arguments
UI::DatePicker.new(name: nil, value: nil, form: nil, field: nil, label: nil, required: false, error: nil, disabled: false, hint: true, min: nil, max: nil, class: nil, **html_attrs)
Notes
UI::DatePicker — a typed date field with a calendar beside it.
Three inputs in one: what the user types (DD/MM/YYYY), what the form submits
(ISO, hidden), and the calendar. The typed field comes first because people
who know the date type it in a second: reaching a date years away in a
calendar means clicking back month after month.
The written format is not decided here. It comes from `date.formats.typed`
in the locale, and this component derives the placeholder, the parse order
and the separator from that one string — so a locale that writes dates
differently changes what the field accepts, what it shows and what the hint
says, together, with nothing hardcoded.
The shipped English format is day-first; change `date.formats.typed` for
month-first. A native date input follows the browser's locale instead, which
is how the same form reads MM/DD on one machine and DD/MM on another, and how
the 2nd of May gets recorded as the 5th of February.
Cally is ~9 KB gzipped and loaded only when the calendar is first opened, so
a form nobody opens a calendar on downloads nothing.
Usage:
<%= render(UI::DatePicker.new(form: f, field: :starts_on, required: true)) %>
<%= render(UI::DatePicker.new(name: "event[date]", value: @date, label: "Date")) %>