Date Picker

A date field for an ordinary Django form — a button that opens a calendar and a hidden input that carries the ISO date to the server.

The button shows the date the way the active locale writes it. What goes to the server is the ISO date, in a hidden input.

Installation

uvx django_shadcn@latest add date_picker

Usage

<form method="post">
    {% csrf_token %}
    <c-date-picker name="due_date" value="{{ form.due_date.value|date:'Y-m-d' }}" />
    <c-button type="submit">Save</c-button>
</form>
class TaskForm(forms.Form):
    due_date = forms.DateField()

Nothing else is needed on the view side. The hidden input posts 2026-08-13, which is what DateField expects, and it already carries value before Alpine has started — so a form that round-trips with errors keeps the date.

One component, not a recipe

Upstream ships this as a recipe: a popover, a button and a calendar wired together in the page, with useState in the middle holding the date.

Here it is one component, because the wiring is exactly the part a Django form needs to get right. Two representations of the same date have to stay in step — the ISO string the server parses and the human sentence the button shows — and that is not something worth retyping per form.

Under it is still the same composition, and each piece can still be used on its own: Popover, Calendar and Button.

Limiting the range

min and max are handed to the calendar, so the days outside the range are dimmed and cannot be picked:

<c-date-picker name="appointment" min="2026-08-10" max="2026-08-20" />

Props

Prop Default Meaning
name the name the hidden input posts under; leave it out for a display-only picker
value the ISO date selected when the page loads
placeholder Pick a date the button's text while nothing is selected
first_day the locale's 0 for Sunday through 6 for Saturday
min the earliest selectable ISO date
max the latest selectable ISO date

Widening it

Upstream puts the width on the trigger. Here it is on the component itself, so class can change it — a class handed to a component cannot reach an element that component renders further in:

<c-date-picker name="due_date" class="w-full" />

It is w-[240px] by default, as upstream.