Shadwire

Search the documentation

Search the documentation and component pages by title and by content.

Get started
Introduction
Installation
shadwire.json
Theming
Dark mode
Tools
CLI
Agent skill
Registry
llms.txt
Guides
Composition
Styling
Forms
Icons
Accessibility
Localisation
Blocks
Overview
Components
Overview
Accordion
Alert
Alert Dialog
Aspect Ratio
Avatar
Badge
Breadcrumb
Button
Button Group
Calendar
Card
Carousel
Chart
Checkbox
Collapsible
Combobox
Command
Context Menu
Data Table
Date Picker
Dialog
Drawer
Dropdown Menu
Empty
Field
Hover Card
Icon
Input
Input Group
Input OTP
Item
Kbd
Label
Menubar
Native Select
Navigation Menu
Pagination
Popover
Progress
Radio Group
Resizable
Scroll Area
Select
Separator
Sheet
Sidebar
Skeleton
Slider
Sonner
Spinner
Switch
Table
Tabs
Textarea
Toggle
Toggle Group
Tooltip

Date Picker

A Popover with a Calendar inside. There is no Ui::DatePickerComponent: you put the two together yourself.

Installation

Shadwire follows shadcn/ui's open code model: the component's source is copied into your application and becomes yours. Install it with the CLI:

shadwire add date-picker --yes

The date-picker registry item installs these files:

  • app/components/ui_component.rb
  • app/javascript/controllers/ui_date_picker_controller.js
  • vendor/shadwire/shadwire.css

This component uses Stimulus, so the application needs importmap-rails and stimulus-rails with automatic controller loading.

Haven't installed the CLI yet? gem install shadwire, then shadwire init.

Usage

ui_calendar holds the value: it writes the chosen date into a hidden input (name:) and fires ui-calendar:change. The ui-date-picker controller listens for that event, shows the formatted date on the trigger and closes the popover. It only handles display: without it the picker still works, but the trigger never shows what was chosen.

<div data-controller="ui-date-picker" data-ui-date-picker-format-value="long">
  <%= ui_popover do %>
    <%= ui_popover_trigger(variant: :outline, class: "w-[212px] justify-between font-normal") do %>
      <span data-ui-date-picker-target="label" data-empty="true"
            class="data-[empty=true]:text-muted-foreground">Pick a date</span>
      <%= ui_icon("chevron-down", class: "opacity-50") %>
    <% end %>
    <%= ui_popover_content(align: :start, class: "w-auto! p-0!") do %>

The popover needs w-auto! p-0!, because its default w-72 and padding leave too little room for the calendar. In Tailwind v4 the ! goes at the end of the class.

Composition

data-controller="ui-date-picker"   the wiring: formats the label, parses what is typed, closes the popover
`-- Popover
    |-- Popover::Trigger           the visible field: a Button or an InputGroup
    |   `-- [data-ui-date-picker-target="label" | "input"]
    `-- Popover::Content (w-auto! p-0!)
        `-- Calendar               name:/end_name: write the value into the form

Examples

The eight arrangements shadcn/ui documents, translated into ViewComponent + Stimulus.

Default

A button that opens the calendar in a popover and then shows the chosen date.

<%# Date Picker = Popover + Calendar (a recipe, not a new component).
    The ui-date-picker controller writes the chosen date into the label and closes the popover. %>
<div data-controller="ui-date-picker" data-ui-date-picker-format-value="long">
  <%= ui_popover do %>
    <%= ui_popover_trigger(variant: :outline, class: "w-[212px] justify-between font-normal") do %>
      <span data-ui-date-picker-target="label" data-empty="true" class="data-[empty=true]:text-muted-foreground">
        Pick a date
      </span>

With a label

The same trigger inside a Field, tied to the label by id.

<%# The trigger is an ordinary button, so `for:`/`id:` tie it to the field label. %>
<%= ui_field(class: "w-48", data: { controller: "ui-date-picker" }) do %>
  <%= ui_field_label(for: "checkin") { "Date" } %>
  <%= ui_popover do %>
    <%= ui_popover_trigger(id: "checkin", variant: :outline, class: "justify-start font-normal") do %>
      <span data-ui-date-picker-target="label" data-empty="true" class="data-[empty=true]:text-muted-foreground">
        Pick a date
      </span>

Range

Two dates in one trigger. The popover closes after the second date is picked, not the first.

<%# `mode: :range` records both ends: `name:` is the start, `end_name:` the finish. %>
<%= ui_field(class: "w-72", data: { controller: "ui-date-picker", ui_date_picker_format_value: "medium" }) do %>
  <%= ui_field_label(for: "stay") { "Length of stay" } %>
  <%= ui_popover do %>
    <%= ui_popover_trigger(id: "stay", variant: :outline, class: "justify-start gap-2 px-2.5 font-normal") do %>
      <%= ui_icon("calendar", class: "opacity-50") %>
      <span data-ui-date-picker-target="label" data-empty="true" class="truncate data-[empty=true]:text-muted-foreground">
        Pick the dates

Date of birth

Month and year selects in the caption, and max: today, since nobody is born in the future.

<%# `caption_layout: :dropdown` swaps the month label for month and year selects —
    essential when the target is decades away. `max:` bounds the calendar and,
    with it, the range offered in the year select. %>
<%= ui_field(class: "w-52", data: { controller: "ui-date-picker", ui_date_picker_format_value: "short" }) do %>
  <%= ui_field_label(for: "born-on") { "Date of birth" } %>
  <%= ui_popover do %>
    <%= ui_popover_trigger(id: "born-on", variant: :outline, class: "justify-between font-normal") do %>
      <span data-ui-date-picker-target="label" data-empty="true" class="data-[empty=true]:text-muted-foreground">

With a text field

Type a date or pick one in the calendar. What you type is parsed, and the field then shows the date formatted. ↓ opens the popover.

<%# Text field + calendar: whatever is typed gets parsed and moves the calendar
    to that date; whatever is picked in the calendar comes back formatted into
    the field. ↓ in the field opens the popover. %>
<%= ui_field(class: "w-60", data: { controller: "ui-date-picker" }) do %>
  <%= ui_field_label(for: "subscribed-on") { "Subscription date" } %>
  <%= ui_input_group do %>
    <%= ui_input_group_input(
          id: "subscribed-on",

Date and time

The picker sets the day and a native input type="time" sets the time.

<%# Date and time are two fields: the picker settles the day, a native
    `input type="time"` settles the hour. The browser's own indicator is hidden
    so it does not compete with the field's icon. %>
<div class="flex max-w-xs gap-4">
  <%= ui_field(data: { controller: "ui-date-picker" }) do %>
    <%= ui_field_label(for: "meeting-on") { "Date" } %>
    <%= ui_popover do %>
      <%= ui_popover_trigger(id: "meeting-on", variant: :outline, class: "w-36 justify-between font-normal") do %>

Natural language

Understands "today", "tomorrow", "in 2 weeks" and "next friday". The list of phrases is in the controller, so you can add your own.

Your post goes out on .

<%# `natural_language_value: true` turns on the controller's relative vocabulary:
    "today", "tomorrow", "in 2 weeks", "next friday" — alongside the 08/01/2026,
    2026-08-01 and "August 1, 2026" formats. The phrase table is your code: edit
    the controller to suit the language you publish in. %>
<%= ui_field(class: "max-w-xs", data: { controller: "ui-date-picker", ui_date_picker_natural_language_value: "true" }) do %>
  <%= ui_field_label(for: "publish-on") { "Schedule publication" } %>
  <%= ui_input_group do %>
    <%= ui_input_group_input(

RTL

dir: :rtl mirrors the grid, the arrows and the ←/→ keys; month_names:/day_names: translate the caption.

<%# `dir: :rtl` mirrors the grid, the navigation arrows and the ←/→ keys.
    `month_names:`/`day_names:` translate the caption and the column headers;
    `lang` and `locale-value` take care of the formatted date and the ARIA labels. %>
<% month_names = %w[يناير فبراير مارس أبريل مايو يونيو يوليو أغسطس سبتمبر أكتوبر نوفمبر ديسمبر] %>
<% day_names = %w[أحد إثنين ثلاثاء أربعاء خميس جمعة سبت] %>

<div dir="rtl" lang="ar" data-controller="ui-date-picker" data-ui-date-picker-locale-value="ar-EG">
  <%= ui_popover do %>

API reference

What the calendar accepts is documented under Calendar. The table below only covers the controller that connects the two: the data-* attributes on the element that wraps the popover and the field.

Attribute Default Description
format-value long full, long, medium, short or iso.
locale-value — BCP 47 (pt-BR, ar-EG). Empty follows the browser's language.
separator-value  –  What separates the two ends of a range in the label.
date-order-value dmy How to read 01/06/2026 as you type. mdy for the US order.
close-on-select-value true Closes the popover on selection. With mode: :range, only once the second date lands.
natural-language-value false Accepts relative phrases (“tomorrow”, “in 2 weeks”, “next friday”) in the text field.
target="label" — The element whose text becomes the chosen date; it gets data-empty while empty.
target="input" — An editable text field: what you type is parsed, and ↓ opens the popover.
target="preview" — An optional echo of the parsed date, for a supporting line below the field.