Date Picker
Enables users to select dates using an input field and calendar interface.
<script lang="ts">
import { DatePicker } from "bits-ui";
import CalendarBlank from "phosphor-svelte/lib/CalendarBlank";
import CaretLeft from "phosphor-svelte/lib/CaretLeft";
import CaretRight from "phosphor-svelte/lib/CaretRight";
</script>
<DatePicker.Root weekdayFormat="short" fixedWeeks={true}>
<div class="flex w-full max-w-[232px] flex-col gap-1.5">
<DatePicker.Label class="block text-sm font-medium select-none"
>Birthday</DatePicker.Label
>
<DatePicker.Input
class="flex h-input w-full max-w-[232px] items-center rounded-input border border-border-input bg-background px-2 py-3 text-sm tracking-[0.01em] text-foreground select-none focus-within:border-border-input-hover focus-within:shadow-date-field-focus hover:border-border-input-hover"
>
{#snippet children({ segments })}
{#each segments as { part, value }, i (part + i)}
<div class="inline-block select-none">
{#if part === "literal"}
<DatePicker.Segment {part} class="p-1 text-muted-foreground">
{value}
</DatePicker.Segment>
{:else}
<DatePicker.Segment
{part}
class="rounded-5px px-1 py-1 hover:bg-muted focus:bg-muted focus:text-foreground focus-visible:ring-0! focus-visible:ring-offset-0! aria-[valuetext=Empty]:text-muted-foreground"
>
{value}
</DatePicker.Segment>
{/if}
</div>
{/each}
<DatePicker.Trigger
class="ml-auto inline-flex size-8 items-center justify-center rounded-[5px] text-foreground/60 transition-all hover:bg-muted active:bg-dark-10"
>
<CalendarBlank class="size-6" />
</DatePicker.Trigger>
{/snippet}
</DatePicker.Input>
<DatePicker.Content sideOffset={6} class="z-50">
<DatePicker.Calendar
class="rounded-[15px] border border-dark-10 bg-background-alt p-[22px] shadow-popover"
>
{#snippet children({ months, weekdays })}
<DatePicker.Header class="flex items-center justify-between">
<DatePicker.PrevButton
class="inline-flex size-10 items-center justify-center rounded-9px bg-background-alt transition-all hover:bg-muted active:scale-[0.98]"
>
<CaretLeft class="size-6" />
</DatePicker.PrevButton>
<DatePicker.Heading class="text-[15px] font-medium" />
<DatePicker.NextButton
class="inline-flex size-10 items-center justify-center rounded-9px bg-background-alt transition-all hover:bg-muted active:scale-[0.98]"
>
<CaretRight class="size-6" />
</DatePicker.NextButton>
</DatePicker.Header>
<div
class="flex flex-col space-y-4 pt-4 sm:flex-row sm:space-y-0 sm:space-x-4"
>
{#each months as month (month.value)}
<DatePicker.Grid
class="w-full border-collapse space-y-1 select-none"
>
<DatePicker.GridHead>
<DatePicker.GridRow class="mb-1 flex w-full justify-between">
{#each weekdays as day (day)}
<DatePicker.HeadCell
class="w-10 rounded-md text-xs font-normal! text-muted-foreground"
>
<div>{day.slice(0, 2)}</div>
</DatePicker.HeadCell>
{/each}
</DatePicker.GridRow>
</DatePicker.GridHead>
<DatePicker.GridBody>
{#each month.weeks as weekDates (weekDates)}
<DatePicker.GridRow class="flex w-full">
{#each weekDates as date (date)}
<DatePicker.Cell
{date}
month={month.value}
class="relative size-10 p-0! text-center text-sm"
>
<DatePicker.Day
class="group relative inline-flex size-10 items-center justify-center rounded-9px border border-transparent bg-transparent p-0 text-sm font-normal whitespace-nowrap text-foreground transition-all hover:border-foreground data-disabled:pointer-events-none data-disabled:text-foreground/30 data-outside-month:pointer-events-none data-selected:bg-foreground data-selected:font-medium data-selected:text-background data-unavailable:text-muted-foreground data-unavailable:line-through"
>
<div
class="absolute top-[5px] hidden size-1 rounded-full bg-foreground transition-all group-data-selected:bg-background group-data-today:block"
></div>
{date.day}
</DatePicker.Day>
</DatePicker.Cell>
{/each}
</DatePicker.GridRow>
{/each}
</DatePicker.GridBody>
</DatePicker.Grid>
{/each}
</div>
{/snippet}
</DatePicker.Calendar>
</DatePicker.Content>
</div>
</DatePicker.Root>
@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: "Cal Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/CalSans-SemiBold.woff2") format("woff2");
}
:root {
/* Colors */
--background: hsl(0 0% 100%);
--background-alt: hsl(0 0% 100%);
--foreground: hsl(0 0% 9%);
--foreground-alt: hsl(0 0% 32%);
--muted: hsl(240 5% 96%);
--muted-foreground: hsla(0 0% 9% / 0.4);
--border: hsl(240 6% 10%);
--border-input: hsla(240 6% 10% / 0.17);
--border-input-hover: hsla(240 6% 10% / 0.4);
--border-card: hsla(240 6% 10% / 0.1);
--dark: hsl(240 6% 10%);
--dark-10: hsla(240 6% 10% / 0.1);
--dark-40: hsla(240 6% 10% / 0.4);
--dark-04: hsla(240 6% 10% / 0.04);
--accent: hsl(204 94% 94%);
--accent-foreground: hsl(204 80% 16%);
--destructive: hsl(347 77% 50%);
--tertiary: hsl(37.7 92.1% 50.2%);
--line: hsl(0 0% 100%);
/* black */
--contrast: hsl(0 0% 0%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.04) inset;
--shadow-popover: 0px 7px 12px 3px hsla(var(--dark-10));
--shadow-kbd: 0px 2px 0px 0px rgba(0, 0, 0, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.03);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.04);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(24, 24, 27, 0.17);
}
.dark {
/* Colors */
--background: hsl(0 0% 5%);
--background-alt: hsl(0 0% 8%);
--foreground: hsl(0 0% 95%);
--foreground-alt: hsl(0 0% 70%);
--muted: hsl(240 4% 16%);
--muted-foreground: hsla(0 0% 100% / 0.4);
--border: hsl(0 0% 96%);
--border-input: hsla(0 0% 96% / 0.17);
--border-input-hover: hsla(0 0% 96% / 0.4);
--border-card: hsla(0 0% 96% / 0.1);
--dark: hsl(0 0% 96%);
--dark-40: hsl(0 0% 96% / 0.4);
--dark-10: hsl(0 0% 96% / 0.1);
--dark-04: hsl(0 0% 96% / 0.04);
--accent: hsl(204 90% 90%);
--accent-foreground: hsl(204 94% 94%);
--destructive: hsl(350 89% 60%);
--line: hsl(0 0% 9.02%);
--tertiary: hsl(61.3 100% 82.2%);
/* white */
--contrast: hsl(0 0% 100%);
/* Shadows */
--shadow-mini: 0px 1px 0px 1px rgba(0, 0, 0, 0.3);
--shadow-mini-inset: 0px 1px 0px 0px rgba(0, 0, 0, 0.5) inset;
--shadow-popover: 0px 7px 12px 3px hsla(0deg 0% 0% / 30%);
--shadow-kbd: 0px 2px 0px 0px rgba(255, 255, 255, 0.07);
--shadow-btn: 0px 1px 0px 1px rgba(0, 0, 0, 0.2);
--shadow-card: 0px 2px 0px 1px rgba(0, 0, 0, 0.4);
--shadow-date-field-focus: 0px 0px 0px 3px rgba(244, 244, 245, 0.1);
}
@theme inline {
--color-background: var(--background);
--color-background-alt: var(--background-alt);
--color-foreground: var(--foreground);
--color-foreground-alt: var(--foreground-alt);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border-card);
--color-border-input: var(--border-input);
--color-border-input-hover: var(--border-input-hover);
--color-border-card: var(--border-card);
--color-dark: var(--dark);
--color-dark-10: var(--dark-10);
--color-dark-40: var(--dark-40);
--color-dark-04: var(--dark-04);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-tertiary: var(--tertiary);
--color-line: var(--line);
--color-contrast: var(--contrast);
--shadow-mini: var(--shadow-mini);
--shadow-mini-inset: var(--shadow-mini-inset);
--shadow-popover: var(--shadow-popover);
--shadow-kbd: var(--shadow-kbd);
--shadow-btn: var(--shadow-btn);
--shadow-card: var(--shadow-card);
--shadow-date-field-focus: var(--shadow-date-field-focus);
--text-xxs: 10px;
--radius-card: 16px;
--radius-card-lg: 20px;
--radius-card-sm: 10px;
--radius-input: 9px;
--radius-button: 5px;
--radius-5px: 5px;
--radius-9px: 9px;
--radius-10px: 10px;
--radius-15px: 15px;
--spacing-input: 3rem;
--spacing-input-sm: 2.5rem;
--breakpoint-desktop: 1440px;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-caret-blink: caret-blink 1s ease-out infinite;
--animate-scale-in: scale-in 0.2s ease;
--animate-scale-out: scale-out 0.15s ease;
--animate-fade-in: fade-in 0.2s ease;
--animate-fade-out: fade-out 0.15s ease;
--animate-enter-from-left: enter-from-left 0.2s ease;
--animate-enter-from-right: enter-from-right 0.2s ease;
--animate-exit-to-left: exit-to-left 0.2s ease;
--animate-exit-to-right: exit-to-right 0.2s ease;
--font-sans: "Inter", "sans-serif";
--font-mono: "Source Code Pro", "monospace";
--font-alt: "Courier", "sans-serif";
--font-display: "Cal Sans", "sans-serif";
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--bits-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--bits-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
@keyframes enter-from-right {
from {
opacity: 0;
transform: translateX(200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-from-left {
from {
opacity: 0;
transform: translateX(-200px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes exit-to-right {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(200px);
}
}
@keyframes exit-to-left {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-200px);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: rotateX(-10deg) scale(0.9);
}
to {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
}
@keyframes scale-out {
from {
opacity: 1;
transform: rotateX(0deg) scale(1);
}
to {
opacity: 0;
transform: rotateX(-10deg) scale(0.95);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border-card, currentColor);
}
* {
@apply border-border;
}
html {
-webkit-text-size-adjust: 100%;
font-variation-settings: normal;
scrollbar-color: var(--bg-muted);
}
body {
@apply bg-background text-foreground;
font-feature-settings:
"rlig" 1,
"calt" 1;
}
::selection {
background: #fdffa4;
color: black;
}
}
@layer components {
*:not(body):not(.focus-override) {
outline: none !important;
&:focus-visible {
@apply focus-visible:ring-foreground focus-visible:ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1;
}
}
.link {
@apply hover:text-foreground/80 focus-visible:ring-foreground focus-visible:ring-offset-background rounded-xs focus-visible:outline-hidden inline-flex items-center gap-1 font-medium underline underline-offset-4 focus-visible:ring-2 focus-visible:ring-offset-2;
}
}
Heads up!
Before diving into this component, it's important to understand how dates/times work in Bits UI. Please read the Dates documentation to learn more!
Structure
<script lang="ts">
import { DatePicker } from "bits-ui";
</script>
<DatePicker.Root>
<DatePicker.Label />
<DatePicker.Input>
{#snippet children({ segments })}
{#each segments as { part, value }}
<DatePicker.Segment {part}>
{value}
</DatePicker.Segment>
{/each}
<DatePicker.Trigger />
{/snippet}
</DatePicker.Input>
<DatePicker.Content>
<DatePicker.Calendar>
{#snippet children({ months, weekdays })}
<DatePicker.Header>
<DatePicker.PrevButton />
<DatePicker.Heading />
<DatePicker.NextButton />
</DatePicker.Header>
{#each months as month}
<DatePicker.Grid>
<DatePicker.GridHead>
<DatePicker.GridRow>
{#each weekdays as day}
<DatePicker.HeadCell>
{day}
</DatePicker.HeadCell>
{/each}
</DatePicker.GridRow>
</DatePicker.GridHead>
<DatePicker.GridBody>
{#each month.weeks as weekDates}
<DatePicker.GridRow>
{#each weekDates as date}
<DatePicker.Cell {date} month={month.value}>
<DatePicker.Day />
</DatePicker.Cell>
{/each}
</DatePicker.GridRow>
{/each}
</DatePicker.GridBody>
</DatePicker.Grid>
{/each}
{/snippet}
</DatePicker.Calendar>
</DatePicker.Content>
</DatePicker.Root>
Managing Placeholder State
This section covers how to manage the placeholder state of the component.
Two-Way Binding
Use bind:placeholder for simple, automatic state synchronization:
<script lang="ts">
import { DatePicker } from "bits-ui";
import { CalendarDateTime } from "@internationalized/date";
let myPlaceholder = $state();
</script>
<button
onclick={() => {
myPlaceholder = new CalendarDateTime(2024, 8, 3, 12, 30);
}}
>
Set placeholder to August 3rd, 2024
</button>
<DatePicker.Root bind:placeholder={myPlaceholder}>
<!-- ... -->
</DatePicker.Root>
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { DatePicker } from "bits-ui";
import type { DateValue } from "@internationalized/date";
let myPlaceholder = $state<DateValue>();
function getPlaceholder() {
return myPlaceholder;
}
function setPlaceholder(newPlaceholder: DateValue) {
myPlaceholder = newPlaceholder;
}
</script>
<DatePicker.Root bind:placeholder={getPlaceholder, setPlaceholder}>
<!-- ... -->
</DatePicker.Root>
Managing Value State
This section covers how to manage the value state of the component.
Two-Way Binding
Use bind:value for simple, automatic state synchronization:
<script lang="ts">
import { DatePicker } from "bits-ui";
import { CalendarDateTime } from "@internationalized/date";
let myValue = $state(new CalendarDateTime(2024, 8, 3, 12, 30));
</script>
<button onclick={() => (myValue = myValue.add({ days: 1 }))}>
Add 1 day
</button>
<DatePicker.Root bind:value={myValue}>
<!-- ... -->
</DatePicker.Root>
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { DatePicker } from "bits-ui";
import type { DateValue } from "@internationalized/date";
let myValue = $state<DateValue>();
function getValue() {
return myValue;
}
function setValue(newValue: DateValue) {
myValue = newValue;
}
</script>
<DatePicker.Root bind:value={getValue, setValue}>
<!-- ... -->
</DatePicker.Root>
Managing Open State
This section covers how to manage the open state of the component.
Two-Way Binding
Use bind:open for simple, automatic state synchronization:
<script lang="ts">
import { DatePicker } from "bits-ui";
let isOpen = $state(false);
</script>
<button onclick={() => (isOpen = true)}>Open DatePicker</button>
<DatePicker.Root bind:open={isOpen}>
<!-- ... -->
</DatePicker.Root>
Fully Controlled
Use a Function Binding for complete control over the state's reads and writes.
<script lang="ts">
import { DatePicker } from "bits-ui";
let myOpen = $state(false);
function getOpen() {
return myOpen;
}
function setOpen(newOpen: boolean) {
myOpen = newOpen;
}
</script>
<DatePicker.Root bind:open={getOpen, setOpen}>
<!-- ... -->
</DatePicker.Root>
Customization
The DatePicker component is made up of three other Bits UI components: Date Field, Calendar, and Popover.
You can check out the documentation for each of these components to learn more about their customization options, each of which can be used to customize the DatePicker component.
API Reference
The root date picker component.
| Property | Details |
|---|---|
value | |
onValueChange | |
open | |
onOpenChange | |
onOpenChangeComplete | |
placeholder | |
onPlaceholderChange | |
isDateUnavailable | |
isDateDisabled | |
validate | |
onInvalid | |
required | |
errorMessageId | |
readonlySegments | |
disableDaysOutsideMonth | |
closeOnDateSelect | |
pagedNavigation | |
preventDeselect | |
weekStartsOn | |
weekdayFormat | |
calendarLabel | |
fixedWeeks | |
maxValue | |
minValue | |
locale | |
numberOfMonths | |
disabled | |
readonly | |
hourCycle | |
granularity | |
hideTimeZone | |
initialFocus | |
monthFormat | |
yearFormat | |
children |
The label for the date field.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-invalid | |
data-disabled | |
data-date-field-label |
The field input component which contains the segments of the date field.
| Property | Details |
|---|---|
ref | |
children | |
child | |
name |
| Data Attribute | Details |
|---|---|
data-invalid | |
data-disabled | |
data-date-field-input |
A segment of the date field.
| Property | Details |
|---|---|
part | |
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-invalid | |
data-disabled | |
data-readonly | |
data-segment | |
data-date-field-segment |
A component which toggles the opening and closing of the popover on press.
| Property | Details |
|---|---|
openOnHover | |
openDelay | |
closeDelay | |
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-state | |
data-popover-trigger |
The contents of the popover which are displayed when the popover is open.
| Property | Details |
|---|---|
side | |
sideOffset | |
align | |
alignOffset | |
arrowPadding | |
avoidCollisions | |
collisionBoundary | |
collisionPadding | |
sticky | |
hideWhenDetached | |
updatePositionStrategy | |
strategy | |
preventScroll | |
customAnchor | |
onInteractOutside | |
onFocusOutside | |
interactOutsideBehavior | |
onEscapeKeydown | |
escapeKeydownBehavior | |
onOpenAutoFocus | |
onCloseAutoFocus | |
trapFocus | |
preventOverflowTextSelection | |
forceMount | |
dir | |
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-state | |
data-starting-style | |
data-ending-style | |
data-popover-content |
| CSS Variable | Details |
|---|---|
--bits-popover-content-transform-origin | |
--bits-popover-content-available-width | |
--bits-popover-content-available-height | |
--bits-popover-anchor-width | |
--bits-popover-anchor-height |
When used, will render the popover content into the body or custom to element when open
| Property | Details |
|---|---|
to | |
disabled | |
children |
The calendar component containing the grids of dates.
| Data Attribute | Details |
|---|---|
data-invalid | |
data-disabled | |
data-readonly | |
data-calendar-root |
The header of the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-header |
The previous button of the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-calendar-prev-button |
The heading of the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-heading |
The next button of the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-calendar-next-button |
The grid of dates in the calendar, typically representing a month.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-grid |
A row in the grid of dates in the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-grid-row |
The head of the grid of dates in the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-grid-head |
A cell in the head of the grid of dates in the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-head-cell |
The body of the grid of dates in the calendar.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-readonly | |
data-calendar-grid-body |
A cell in the calendar grid.
| Property | Details |
|---|---|
date | |
month | |
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-unavailable | |
data-today | |
data-outside-month | |
data-outside-visible-months | |
data-focused | |
data-selected | |
data-value | |
data-calendar-cell |
A day in the calendar grid.
| Property | Details |
|---|---|
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-unavailable | |
data-today | |
data-outside-month | |
data-outside-visible-months | |
data-focused | |
data-selected | |
data-value | |
data-calendar-day |
A select you can use to navigate to a specific month in the calendar view.
| Property | Details |
|---|---|
months | |
monthFormat | |
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-calendar-month-select |
A select you can use to navigate to a specific year in the calendar view.
| Property | Details |
|---|---|
years | |
yearFormat | |
ref | |
children | |
child |
| Data Attribute | Details |
|---|---|
data-disabled | |
data-calendar-year-select |