Date field
A native date input with a calendar icon and an optional hint line below it.
npx shadcn@latest add https://ui.goodtabs.co/r/date-field.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/date-field
One field with a label, no hint.
With min and max
Between 1 Nov and 31 Dec 2026
Any day in November or December.
A bounded field with a hint stating the allowed range.
Range
Two fields side by side, each bounded by the other.
Usage
import { DateField } from "@/components/ui/date-field"
<Label htmlFor="due">Due date</Label>
<DateField id="due" min="2026-11-01" onChange={(e) => setDue(e.target.value)} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| hint | ReactNode | A line under the field, for example the allowed range. Off by default because the browser formats the value for its locale. | |
| min | string | Earliest date as YYYY-MM-DD. Native Input prop. | |
| max | string | Latest date as YYYY-MM-DD. Native Input prop. | |
| value, defaultValue, onChange | Input props | Controlled or uncontrolled, always as a YYYY-MM-DD string. |
Source
date-field/date-field.tsx
import * as React from "react";
import { CalendarIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
type DateFieldProps = Omit<React.ComponentProps<typeof Input>, "type"> & {
/** A hint under the field, for example the range or the format. The browser formats the value for its locale, so only state a format when you control it. */
hint?: React.ReactNode;
};
/**
* A native date input with a calendar icon and an optional mono hint line.
* Native on purpose: it gets the platform picker, keyboard entry and screen reader support for free.
*/
export function DateField({ hint, className, id, "aria-describedby": describedBy, ...props }: DateFieldProps) {
const generated = React.useId();
const inputId = id ?? generated;
const hintId = `${inputId}-hint`;
return (
<div data-slot="date-field" className={cn("flex w-full flex-col gap-1.5", className)}>
<div className="relative flex items-center">
<CalendarIcon
className="pointer-events-none absolute left-2.5 size-4 text-muted-foreground"
aria-hidden="true"
/>
<Input
type="date"
id={inputId}
aria-describedby={cn(hint && hintId, describedBy) || undefined}
className="relative pl-8 tabular-nums [&::-webkit-calendar-picker-indicator]:absolute [&::-webkit-calendar-picker-indicator]:inset-y-0 [&::-webkit-calendar-picker-indicator]:left-0 [&::-webkit-calendar-picker-indicator]:m-0 [&::-webkit-calendar-picker-indicator]:w-8 [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:opacity-0 [&::-webkit-date-and-time-value]:text-left"
{...props}
/>
</div>
{hint && (
<span id={hintId} className="font-mono text-xs text-muted-foreground">
{hint}
</span>
)}
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/date-field/prompt.md, or the Copy AI prompt button above.
When to use
- Due dates, birthdays, start and end dates in a form
- Anywhere a single day is enough and the platform picker is welcome
- Date ranges, as two fields bounded by each other
When not to use
- Picking a range on one calendar or showing availability: build a custom calendar
- Date and time together, which needs type="datetime-local" and its own layout
- Anything that must look the same in every browser; native pickers differ by platform
Agent notes
- Server component: it holds no state. It extends shadcn's Input, so value, min, max, disabled and onChange pass through
- The value is always a YYYY-MM-DD string whatever the display locale; the browser displays it in the user's locale, so do not promise a display format in a hint
- The native picker glyph is hidden and its hit area sits over the calendar icon, so clicking the icon still opens the picker in Chromium
- Pair it with a Label through id; the hint is linked by aria-describedby when shown