---
title: Date Picker
description: A date picker component with range and presets.
---

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/calendar"
	"github.com/axadrn/shadcn-templ/v2/components/icon"
	"github.com/axadrn/shadcn-templ/v2/components/popover"
)

templ DatePickerDemo() {
	@popover.Popover(popover.Props{ID: "date-picker-demo"}) {
		{{
			triggerAttrs := popover.Trigger(ctx)
			triggerAttrs["data-empty"] = "true"
		}}
		@button.Button(button.Props{
			Variant:    button.VariantOutline,
			Class:      "w-[212px] justify-between text-left font-normal data-[empty=true]:text-muted-foreground",
			Attributes: triggerAttrs,
		}) {
			<span>Pick a date</span>
			<span data-icon="inline-end" class="contents">
				@icon.ChevronDown()
			</span>
		}
		@popover.Content(popover.ContentProps{
			Class:     "w-auto p-0",
			Side: popover.SideBottom, Align: popover.AlignStart,
		}) {
			@calendar.Calendar()
		}
	}
	<script>
		(() => {
			const fmt = (d) => {
				const day = d.getDate();
				const suf = day % 10 === 1 && day !== 11 ? "st" : day % 10 === 2 && day !== 12 ? "nd" : day % 10 === 3 && day !== 13 ? "rd" : "th";
				return d.toLocaleDateString("en-US", { month: "long" }) + " " + day + suf + ", " + d.getFullYear();
			};
			// Delegated: the popover content is portaled to <body> at init, so it
			// is looked up per event instead of captured at parse time.
			document.addEventListener("calendar-change", (e) => {
				if (!(e.target instanceof Element) || !e.target.closest("#date-picker-demo")) return;
				const trigger = document.querySelector('[aria-controls="date-picker-demo"]');
				trigger.querySelector("span").textContent = e.detail.value ? fmt(e.detail.value) : "Pick a date";
				trigger.setAttribute("data-empty", e.detail.value ? "false" : "true");
			});
		})();
	</script>
}
```

## Installation

The Date Picker is built using a composition of the `Popover` and the `Calendar` components.

See installation instructions for the [Popover](/docs/components/popover#installation) and the [Calendar](/docs/components/calendar#installation) components.

## Usage

```templ showLineNumbers title="components/example_date_picker.templ"
@popover.Popover(popover.Props{ID: "date-picker"}) {
	{{
		triggerAttrs := popover.Trigger(ctx)
		triggerAttrs["data-empty"] = "true"
		triggerAttrs["data-tui-datepicker-display"] = ""
	}}
	@button.Button(button.Props{
		Variant:    button.VariantOutline,
		Class:      "w-[212px] justify-between text-left font-normal data-[empty=true]:text-muted-foreground",
		Attributes: triggerAttrs,
	}) {
		<span>Pick a date</span>
		<span data-icon="inline-end" class="contents">
			@icon.ChevronDown()
		</span>
	}
	@popover.Content(popover.ContentProps{
		Class: "w-auto p-0",
		Side:  popover.SideBottom,
		Align: popover.AlignStart,
	}) {
		@calendar.Calendar()
	}
}
```

Listen for the calendar's `calendar-change` event to show the selected date in the trigger, see the demo above for the full wiring.

## Composition

A date picker is built from `Popover` and `Calendar` (there is no DatePicker root component):

```text
popover.Popover
├── popover.Trigger
└── popover.Content
    └── calendar.Calendar
```

## Basic

A basic date picker component.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/calendar"
	"github.com/axadrn/shadcn-templ/v2/components/field"
	"github.com/axadrn/shadcn-templ/v2/components/popover"
)

templ DatePickerBasic() {
	@field.Field(field.Props{Class: "mx-auto w-44"}) {
		@field.Label(field.LabelProps{For: "date-picker-simple"}) {
			Date
		}
		@popover.Popover(popover.Props{ID: "date-picker-basic"}) {
		{{
			triggerAttrs := popover.Trigger(ctx)
			triggerAttrs["id"] = "date-picker-simple"
		}}
			@button.Button(button.Props{
				Variant:    button.VariantOutline,
				Class:      "justify-start font-normal",
				Attributes: triggerAttrs,
			}) {
				<span>Pick a date</span>
			}
			@popover.Content(popover.ContentProps{
				Class:     "w-auto p-0",
				Side: popover.SideBottom, Align: popover.AlignStart,
			}) {
				@calendar.Calendar()
			}
		}
	}
	<script>
		(() => {
			const fmt = (d) => {
				const day = d.getDate();
				const suf = day % 10 === 1 && day !== 11 ? "st" : day % 10 === 2 && day !== 12 ? "nd" : day % 10 === 3 && day !== 13 ? "rd" : "th";
				return d.toLocaleDateString("en-US", { month: "long" }) + " " + day + suf + ", " + d.getFullYear();
			};
			// Delegated: the popover content is portaled to <body> at init, so it
			// is looked up per event instead of captured at parse time.
			document.addEventListener("calendar-change", (e) => {
				if (!(e.target instanceof Element) || !e.target.closest("#date-picker-basic")) return;
				const trigger = document.querySelector('[aria-controls="date-picker-basic"]');
				trigger.querySelector("span").textContent = e.detail.value ? fmt(e.detail.value) : "Pick a date";
			});
		})();
	</script>
}
```

## Range Picker

A date picker component for selecting a range of dates.

```templ
package examples

import (
	"time"

	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/calendar"
	"github.com/axadrn/shadcn-templ/v2/components/field"
	"github.com/axadrn/shadcn-templ/v2/components/icon"
	"github.com/axadrn/shadcn-templ/v2/components/popover"
)

templ DatePickerRange() {
	{{
		from := time.Date(time.Now().Year(), 1, 20, 0, 0, 0, 0, time.Local)
		to := from.AddDate(0, 0, 20)
	}}
	@field.Field(field.Props{Class: "mx-auto w-60"}) {
		@field.Label(field.LabelProps{For: "date-picker-range-trigger"}) {
			Date Picker Range
		}
		@popover.Popover(popover.Props{ID: "date-picker-range"}) {
		{{
			triggerAttrs := popover.Trigger(ctx)
			triggerAttrs["id"] = "date-picker-range-trigger"
		}}
			@button.Button(button.Props{
				Variant:    button.VariantOutline,
				Class:      "justify-start px-2.5 font-normal",
				Attributes: triggerAttrs,
			}) {
				<span data-icon="inline-start" class="contents">
					@icon.Calendar()
				</span>
				<span>
					{ from.Format("Jan 02, 2006") } - { to.Format("Jan 02, 2006") }
				</span>
			}
			@popover.Content(popover.ContentProps{
				Class:     "w-auto p-0",
				Side: popover.SideBottom, Align: popover.AlignStart,
			}) {
				@calendar.Calendar(calendar.Props{
					Mode:           calendar.ModeRange,
					Selected:          from,
					EndValue:       to,
					NumberOfMonths: 2,
				})
			}
		}
	}
	<script>
		(() => {
			const fmt = (d) => d.toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric" });
			// Delegated: the popover content is portaled to <body> at init, so it
			// is looked up per event instead of captured at parse time.
			document.addEventListener("calendar-change", (e) => {
				if (!(e.target instanceof Element) || !e.target.closest("#date-picker-range")) return;
				const trigger = document.querySelector('[aria-controls="date-picker-range"]');
				const display = trigger.querySelector("span:not([data-icon])");
				if (e.detail.value && e.detail.endValue) {
					display.textContent = fmt(e.detail.value) + " - " + fmt(e.detail.endValue);
				} else if (e.detail.value) {
					display.textContent = fmt(e.detail.value);
				} else {
					display.textContent = "Pick a date";
				}
			});
		})();
	</script>
}
```

## Date of Birth

A date picker component for selecting a date of birth. This component includes a dropdown caption layout for date and month selection.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/calendar"
	"github.com/axadrn/shadcn-templ/v2/components/field"
	"github.com/axadrn/shadcn-templ/v2/components/popover"
)

templ DatePickerDob() {
	@field.Field(field.Props{Class: "mx-auto w-44"}) {
		@field.Label(field.LabelProps{For: "date-picker-dob-trigger"}) {
			Date of birth
		}
		@popover.Popover(popover.Props{ID: "date-picker-dob"}) {
		{{
			triggerAttrs := popover.Trigger(ctx)
			triggerAttrs["id"] = "date-picker-dob-trigger"
		}}
			@button.Button(button.Props{
				Variant:    button.VariantOutline,
				Class:      "justify-start font-normal",
				Attributes: triggerAttrs,
			}) {
				<span>Select date</span>
			}
			@popover.Content(popover.ContentProps{
				Class:     "w-auto overflow-hidden p-0",
				Side: popover.SideBottom, Align: popover.AlignStart,
			}) {
				@calendar.Calendar(calendar.Props{
					CaptionLayout: calendar.CaptionLayoutDropdown,
				})
			}
		}
	}
	<script>
		(() => {
			// Delegated: the popover content is portaled to <body> at init, so it
			// is looked up per event instead of captured at parse time.
			document.addEventListener("calendar-change", (e) => {
				if (!(e.target instanceof Element) || !e.target.closest("#date-picker-dob")) return;
				const trigger = document.querySelector('[aria-controls="date-picker-dob"]');
				trigger.querySelector("span").textContent = e.detail.value ? e.detail.value.toLocaleDateString() : "Select date";
				if (e.detail.value) window.tui.popover.close(e.target.closest("#date-picker-dob"));
			});
		})();
	</script>
}
```

## Input

A date picker component with an input field for selecting a date.

```templ
package examples

import (
	"time"

	"github.com/axadrn/shadcn-templ/v2/components/calendar"
	"github.com/axadrn/shadcn-templ/v2/components/field"
	"github.com/axadrn/shadcn-templ/v2/components/icon"
	"github.com/axadrn/shadcn-templ/v2/components/inputgroup"
	"github.com/axadrn/shadcn-templ/v2/components/popover"
)

templ DatePickerInput() {
	{{
		selected := time.Date(2025, 6, 1, 0, 0, 0, 0, time.Local)
	}}
	@field.Field(field.Props{Class: "mx-auto w-48"}) {
		@field.Label(field.LabelProps{For: "date-picker-input-field"}) {
			Subscription Date
		}
		@popover.Popover(popover.Props{ID: "date-picker-input"}) {
		{{
			triggerAttrs := popover.Trigger(ctx)
			triggerAttrs["data-slot"] = "input-group-button"
			triggerAttrs["aria-label"] = "Select date"
		}}
			@inputgroup.InputGroup() {
				@inputgroup.Input(inputgroup.InputProps{
					ID:          "date-picker-input-field",
					Value:       "June 01, 2025",
					Placeholder: "June 01, 2025",
				})
				@inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) {
					@inputgroup.Button(inputgroup.ButtonProps{
						Size:       inputgroup.ButtonSizeIconXs,
						Attributes: triggerAttrs,
					}) {
						@icon.Calendar()
						<span class="sr-only">Select date</span>
					}
				}
			}
			@popover.Content(popover.ContentProps{
				Class:     "w-auto overflow-hidden p-0",
				Side: popover.SideBottom, Align: popover.AlignEnd,
				SideOffset: 10,
			}) {
				@calendar.Calendar(calendar.Props{
					ID:    "date-picker-input-calendar",
					Selected: selected,
				})
			}
		}
	}
	<script>
		(() => {
			const input = document.getElementById("date-picker-input-field");
			const fmt = (d) => d.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric" });
			// Delegated: the popover content is portaled to <body> at init, so it
			// is looked up per event instead of captured at parse time.
			document.addEventListener("calendar-change", (e) => {
				if (!(e.target instanceof Element) || !e.target.closest("#date-picker-input")) return;
				if (!e.detail.value) return;
				input.value = fmt(e.detail.value);
				window.tui.popover.close(e.target.closest("#date-picker-input"));
			});
			input.addEventListener("input", () => {
				const date = new Date(input.value);
				if (!isNaN(date.getTime())) {
					const iso = date.toLocaleDateString("sv-SE");
					document.getElementById("date-picker-input-calendar").dispatchEvent(
						new CustomEvent("calendar-set", { bubbles: true, detail: { date: iso } }),
					);
				}
			});
			input.addEventListener("keydown", (e) => {
				if (e.key === "ArrowDown") {
					e.preventDefault();
					window.tui.popover.open(document.getElementById("date-picker-input"));
				}
			});
		})();
	</script>
}
```

## Time Picker

A date picker component with a time input field for selecting a time.

```templ
package examples

import (
	"github.com/axadrn/shadcn-templ/v2/components/button"
	"github.com/axadrn/shadcn-templ/v2/components/calendar"
	"github.com/axadrn/shadcn-templ/v2/components/field"
	"github.com/axadrn/shadcn-templ/v2/components/icon"
	"github.com/axadrn/shadcn-templ/v2/components/input"
	"github.com/axadrn/shadcn-templ/v2/components/popover"
)

templ DatePickerTime() {
	@field.Group(field.GroupProps{Class: "mx-auto max-w-xs flex-row"}) {
		@field.Field() {
			@field.Label(field.LabelProps{For: "date-picker-optional"}) {
				Date
			}
			@popover.Popover(popover.Props{ID: "date-picker-time"}) {
		{{
			triggerAttrs := popover.Trigger(ctx)
			triggerAttrs["id"] = "date-picker-optional"
		}}
				@button.Button(button.Props{
					Variant:    button.VariantOutline,
					Class:      "w-32 justify-between font-normal",
					Attributes: triggerAttrs,
				}) {
					<span>Select date</span>
					<span data-icon="inline-end" class="contents">
						@icon.ChevronDown()
					</span>
				}
				@popover.Content(popover.ContentProps{
					Class:     "w-auto overflow-hidden p-0",
					Side: popover.SideBottom, Align: popover.AlignStart,
				}) {
					@calendar.Calendar(calendar.Props{
						CaptionLayout: calendar.CaptionLayoutDropdown,
					})
				}
			}
		}
		@field.Field(field.Props{Class: "w-32"}) {
			@field.Label(field.LabelProps{For: "time-picker-optional"}) {
				Time
			}
			@input.Input(input.Props{
				ID:    "time-picker-optional",
				Type:  "time",
				Value: "10:30:00",
				Class: "appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none",
				Attributes: templ.Attributes{
					"step": "1",
				},
			})
		}
	}
	<script>
		(() => {
			const fmt = (d) => {
				const day = d.getDate();
				const suf = day % 10 === 1 && day !== 11 ? "st" : day % 10 === 2 && day !== 12 ? "nd" : day % 10 === 3 && day !== 13 ? "rd" : "th";
				return d.toLocaleDateString("en-US", { month: "long" }) + " " + day + suf + ", " + d.getFullYear();
			};
			// Delegated: the popover content is portaled to <body> at init, so it
			// is looked up per event instead of captured at parse time.
			document.addEventListener("calendar-change", (e) => {
				if (!(e.target instanceof Element) || !e.target.closest("#date-picker-time")) return;
				const content = e.target.closest("#date-picker-time");
				const trigger = document.querySelector('[aria-controls="date-picker-time"]');
				trigger.querySelector("span").textContent = e.detail.value ? fmt(e.detail.value) : "Select date";
				if (e.detail.value) window.tui.popover.close(content);
			});
		})();
	</script>
}
```
