shadcn.io is not affiliated with official shadcn/ui
Shadcn Date Input for React and Tailwind
Segmented input for dates and times.
mm/dd/yyyy
Installation
bunx --bun shadcn@latest add https://kit.dev/r/date-input.jsonpnpm dlx shadcn@latest add https://kit.dev/r/date-input.jsonnpx shadcn@latest add https://kit.dev/r/date-input.jsonyarn shadcn@latest add https://kit.dev/r/date-input.jsonThis component depends on Input Group. Install it first if you haven't already.
Install the following dependencies:
bun add @ark-ui/react lucide-reactpnpm add @ark-ui/react lucide-reactnpm install @ark-ui/react lucide-reactyarn add @ark-ui/react lucide-reactCopy and paste the following code into your project.
"use client";
import {
DateInput as ArkDateInput,
useDateInput as useArkDateInput,
useDateInputContext as useArkDateInputContext,
} from "@ark-ui/react/date-input";
import { XIcon } from "lucide-react";
import type React from "react";
import { cn } from "@/lib/utils";
import type { InputProps } from "@/components/ui/input";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
type InputGroupProps,
} from "@/components/ui/input-group";
export const useDateInput: typeof useArkDateInput = (props) =>
useArkDateInput({
shouldForceLeadingZeros: true,
...props,
});
export const useDateInputContext = useArkDateInputContext;
export const DateInputContext: typeof ArkDateInput.Context =
ArkDateInput.Context;
interface DateInputLayoutProps {
/**
* The selection mode. Must match the machine option.
*
* @default "single"
*/
selectionMode?: "single" | "range";
/**
* The separator to show between the date input fields in range mode.
*
* @default "-"
*/
separator?: string | React.ReactNode;
/**
* Whether to show the clear button.
*
* @default false
*/
showClear?: boolean;
/**
* The size of the control.
*
* @default "md"
*/
size?: InputProps["size"];
}
interface DateInputProps
extends React.ComponentProps<typeof ArkDateInput.Root>,
DateInputLayoutProps {}
interface DateInputControlProps extends Pick<InputGroupProps, "size"> {
children: React.ReactNode;
showClear: boolean;
}
const dateInputRootClassName = cn(
"group/date-input",
"flex w-full flex-col items-start gap-2"
);
export const DateInput = (props: DateInputProps) => {
const {
size = "md",
showClear = false,
selectionMode = "single",
shouldForceLeadingZeros = true,
separator = "-",
className,
children,
...rest
} = props;
return (
<ArkDateInput.Root
className={cn(dateInputRootClassName, className)}
data-size={size}
data-slot="date-input"
selectionMode={selectionMode}
shouldForceLeadingZeros={shouldForceLeadingZeros}
{...rest}
>
{children}
<DateInputFields
selectionMode={selectionMode}
separator={separator}
showClear={showClear}
size={size}
/>
</ArkDateInput.Root>
);
};
export const DateInputRootProvider = (
props: React.ComponentProps<typeof ArkDateInput.RootProvider> &
DateInputLayoutProps
) => {
const {
size = "md",
showClear = false,
selectionMode = "single",
separator = "-",
className,
children,
...rest
} = props;
return (
<ArkDateInput.RootProvider
className={cn(dateInputRootClassName, className)}
data-size={size}
data-slot="date-input"
{...rest}
>
{children}
<DateInputFields
selectionMode={selectionMode}
separator={separator}
showClear={showClear}
size={size}
/>
</ArkDateInput.RootProvider>
);
};
export const DateInputLabel = (
props: React.ComponentProps<typeof ArkDateInput.Label>
) => {
const { className, ...rest } = props;
return (
<ArkDateInput.Label
className={cn("font-medium text-sm", className)}
data-slot="date-input-label"
{...rest}
/>
);
};
const DateInputFields = (
props: Required<Pick<DateInputLayoutProps, "showClear" | "selectionMode">> &
Pick<DateInputLayoutProps, "separator" | "size">
) => {
const { size = "md", showClear, separator = "-", selectionMode } = props;
return (
<>
<DateInputControl showClear={showClear} size={size}>
<DateInputSegmentGroup index={0} />
{selectionMode === "range" && (
<>
<span
aria-hidden="true"
className="pointer-events-none select-none text-muted-foreground [&_svg]:size-3 [&_svg]:shrink-0"
>
{separator}
</span>
<DateInputSegmentGroup index={1} />
</>
)}
</DateInputControl>
<ArkDateInput.HiddenInput index={0} key="date-input-hidden-0" />
{selectionMode === "range" && (
<ArkDateInput.HiddenInput index={1} key="date-input-hidden-1" />
)}
</>
);
};
const DateInputControl = (props: DateInputControlProps) => {
const { size = "md", showClear, children } = props;
const dateInput = useDateInputContext();
const hasValue = dateInput.value.length > 0;
const handleClear = () => {
dateInput.clearValue();
};
return (
<ArkDateInput.Control asChild data-slot="date-input-control">
<InputGroup
className={cn(
"px-3",
"data-disabled:pointer-events-none data-disabled:opacity-64",
"has-data-[slot=date-input-clear]:pe-0"
)}
size={size}
>
<div
aria-invalid={dateInput.invalid || undefined}
className={cn(
"min-w-0",
"flex flex-1 items-center gap-2",
"text-base md:text-sm"
)}
data-slot="date-input-field"
>
{children}
</div>
{showClear && hasValue && !dateInput.disabled && (
<InputGroupAddon align="inline-end">
<InputGroupButton
aria-label="Clear date"
data-slot="date-input-clear"
onClick={handleClear}
size="icon-xs"
variant="ghost"
>
<XIcon aria-hidden="true" />
</InputGroupButton>
</InputGroupAddon>
)}
</InputGroup>
</ArkDateInput.Control>
);
};
const DateInputSegmentGroup = (
props: React.ComponentProps<typeof ArkDateInput.SegmentGroup>
) => {
const { index = 0, className, ...rest } = props;
return (
<ArkDateInput.SegmentGroup
className={cn("flex min-w-0 grow items-center gap-px", className)}
data-slot="date-input-segment-group"
index={index}
{...rest}
>
<ArkDateInput.SegmentContext>
{(segment) => <DateInputSegment segment={segment} />}
</ArkDateInput.SegmentContext>
</ArkDateInput.SegmentGroup>
);
};
const DateInputSegment = (
props: React.ComponentProps<typeof ArkDateInput.Segment>
) => {
const { className, ...rest } = props;
return (
<ArkDateInput.Segment
className={cn(
"tabular-nums",
"rounded-sm border-0 shadow-none ring-0",
"not-data-[type=literal]:px-0.5",
"not-data-[type=literal]:focus:bg-primary not-data-[type=literal]:focus:text-primary-foreground",
"data-[type=literal]:select-none data-[type=literal]:px-px data-[type=literal]:text-muted-foreground/64",
"data-placeholder-shown:text-muted-foreground/64",
"outline-none",
"data-readonly:cursor-default",
"group-aria-invalid/date-input:text-destructive group-data-invalid/date-input:text-destructive",
"not-data-[type=literal]:focus:group-data-invalid/date-input:bg-destructive not-data-[type=literal]:focus:group-data-invalid/date-input:text-white",
"dark:group-data-invalid/date-input:text-destructive-foreground dark:group-aria-invalid/date-input:text-destructive-foreground",
"dark:not-data-[type=literal]:focus:group-data-invalid/date-input:bg-destructive-foreground dark:not-data-[type=literal]:focus:group-data-invalid/date-input:text-white",
className
)}
data-slot="date-input-segment"
{...rest}
/>
);
};Update the import paths to match your project setup.
Anatomy
DateInput
└── DateInputLabel
├── DateInputControl
├── DateInputSegmentGroup
├── DateInputSegment
└── DateInputHiddenInputUsage
import { DateInput } from "@/components/ui/date-input";const Example = () => <DateInput className="w-full max-w-64" />;Accessibility
| Key | Description |
|---|---|
ArrowLeft | Move left. |
ArrowRight | Move right. |
ArrowUp | Move up. |
ArrowDown | Move down. |
PageUp | Jump backward. |
PageDown | Jump forward. |
Backspace | Delete backward. |
Delete | Delete backward. |
Home | Go to the start. |
End | Go to the end. |
Demos
Context
Controlled
Custom Separator
Default Value
Disabled
Granularity
Invalid
Leading Zeros
Min Max
Range
Read Only
Root Provider
Size Lg
Size Md
Size Sm
Time Only
With Clear Button
With Field
API Reference
DateInput
div
PropType
AttributeType
DateInputLabel
label
PropType
AttributeType
DateInputControl
div
PropType
AttributeType
DateInputSegmentGroup
div
PropType
AttributeType
DateInputSegment
span
PropType
AttributeType
DateInputHiddenInput
input
PropType
AttributeType
DateInputRootProvider
div
PropType
AttributeType