The shadcn/ui date picker page says it has "range and presets." The visible code has no presets, so you will write them yourself. This article covers the range picker the page does show, adds the presets in code, and explains which variant to use for each form. It ends with what OTF can and cannot claim about its kits.
What the shadcn page covers
The page describes the component as "A date picker component with range and presets." It lists six variants: a basic picker, a range picker, a date of birth picker, an input picker, a time picker and a natural language picker. An RTL note points to a separate guide. The natural language variant uses the chrono-node library to parse dates typed as plain language. The other variants need only the date and calendar pieces.
The picker is not a single root component. The page builds it from a Popover that holds a trigger and a Calendar. The usage example puts <Calendar mode="single" selected={date} onSelect={setDate} /> inside a PopoverContent with className="w-auto p-0". The page imports format from date-fns for display and an icon from lucide-react for the trigger. Install the Popover and Calendar components first; the page links to their install steps.
Build the range picker
A range picker needs one state value that holds both ends of the selection. React DayPicker's DateRange type gives you a shape with an optional from and to. Start with an empty range, then pass it to a Calendar in range mode:
import * as React from "react"
import { format } from "date-fns"
import type { DateRange } from "react-day-picker"
import { Calendar } from "@/components/ui/calendar"
export function DateRangeField() {
const [range, setRange] = React.useState<DateRange | undefined>()
return (
<div className="flex flex-col gap-2">
<Calendar
mode="range"
selected={range}
onSelect={setRange}
numberOfMonths={2}
/>
{range?.from && (
<p className="text-sm text-muted-foreground">
{format(range.from, "PPP")}
{range.to ? ` to ${format(range.to, "PPP")}` : ""}
</p>
)}
</div>
)
}
The range.to value stays empty after the first click. That is correct: the user has picked a start date and has not yet picked an end. The display line handles both states, so the form never shows a half-formed range as complete.
Wrap the calendar in a Popover when the field should sit inside a form. The trigger shows the selected range, and the popover opens the calendar. Keep the trigger text as the source of truth for the value users see, so a closed field still shows the dates.
Add presets yourself
The shadcn page does not include preset buttons in its visible code, so presets are not something you can copy from the example. Add them as plain buttons that set the same state the calendar uses. Each preset computes a from and a to date with date-fns:
import * as React from "react"
import { startOfDay, subDays } from "date-fns"
import type { DateRange } from "react-day-picker"
import { Button } from "@/components/ui/button"
const presets = [
{ label: "Last 7 days", days: 7 },
{ label: "Last 30 days", days: 30 },
]
export function RangePresets({
onPick,
}: {
onPick: (range: DateRange) => void
}) {
return (
<div className="flex flex-wrap gap-2">
{presets.map((preset) => (
<Button
key={preset.label}
variant="outline"
size="sm"
onClick={() => {
const today = startOfDay(new Date())
onPick({ from: subDays(today, preset.days), to: today })
}}
>
{preset.label}
</Button>
))}
</div>
)
}
Pass the same setRange you gave the calendar as onPick. A preset then updates the calendar's highlighted days and the display line at the same time, with no second source of truth. startOfDay runs inside the click handler, so each preset ends on the day the user clicks, not the day the component first rendered.
Keep the preset list short. Three to five presets cover most reporting screens, and more than that starts to compete with the calendar for attention. Choose the ranges your users ask for in support tickets or in their own filters, not the ones that look good in a demo.
Pick the right variant for each form
Use the basic picker when a form needs one date, such as a start date for a project. Use the range picker for reports and filters. The date of birth variant changes the caption layout so users can move between years quickly, which matters for a field that may be decades in the past. The input variant pairs the calendar with a text field, useful when users type dates they already know. The time picker adds a time input next to the calendar, for bookings and deadlines.
The natural language variant is the one that needs an extra dependency. It parses typed phrases with chrono-node, so add that package only when you ship that variant. Do not add it to forms that only take a date from a calendar. An extra parser is one more thing to test, and the calendar does the job for most forms.
Whichever variant you pick, store the value as a date and format it only at display time. Saving a formatted string makes sorting and filtering harder later.
Test the edge cases
Date pickers fail in the corners. Check these before you ship:
- Pick a start date, then leave the end empty. The field should show the start date and not claim a finished range.
- Click a preset, then click a date inside the same range. The calendar should change the range without a stale highlight.
- Open the picker near the end of a month. The Last 30 days preset should cross the month boundary and show the right days.
- Tab through the calendar and press Enter on a day. The selection should change without a mouse.
- Open the popover on a narrow phone screen. The calendar should fit, or the popover should scroll, with no horizontal overflow.
Run the time zone check too. startOfDay uses the user's local time zone. If your server stores dates in UTC, convert once at the boundary and keep the rest of the app in one zone.
Keep the picker accessible
A calendar is hard to use with a screen reader when the grid exists only as a visual layout. Make the popover trigger a real button, and give it an accessible name that states the current value, such as "Date range: Oct 1, 2026 to Oct 7, 2026". That way the value is announced before the user opens the calendar. The popover should keep focus while it is open and return focus to the trigger when it closes. The calendar's day cells respond to the arrow keys, which is why the keyboard check above includes Tab and Enter.
Check the highlight color for selected days in both light and dark mode. A faint fill can hide where a range ends, and users who need more contrast will miss it. If the highlight is hard to see, change the fill color before you change the layout, because the layout is rarely the cause.
Where OTF stands
If you want a date range picker already wired into a product, you will not find one we can point to. We have not confirmed that any OTF kit ships the shadcn date picker, so this article does not say one does. The pricing page lists the Full-stack kits at $99 each and the Everything Bundle at $149, both as one-time payments, and it offers a live demo before you buy. Check the kit's repository for the components it ships before you rely on any of them.
The data table guide covers the other common filter control, the sortable and paged table. If you are picking a starting point for a dashboard, the pricing page is the place to compare kits side by side: OTF pricing.
Sources
- shadcn/ui, Date picker: https://ui.shadcn.com/docs/components/date-picker
- OTF pricing: otf-kit.dev/pricing


Top comments (0)