shadimage
bjork-ui
componentprimitive-date-pickersource: ok · hit

Date Picker

Date Picker is a production-ready ui component from BJORK UI, packaged for shadcn registries.

npx shadcn add "https://ui.isaiahbjork.com/primitive-date-picker.json"
free

Where it goes

calendar

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
ui-primitives@radix-ui/react-popover@radix-ui/react-slot
animationframer-motion
iconslucide-react

Files8 · source cached 10/15/2026, 4:38:58 AM

components/bjork-ui/primitives/date-picker.tsx
"use client";

import * as React from "react";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { BjorkButton } from "./button";
import { BjorkCalendar } from "./calendar";
import { bjorkMenu } from "./_shared";

export function BjorkDatePicker() {
  const [date, setDate] = React.useState<Date | undefined>(new Date(2026, 4, 15));

  return (
    <Popover>
      <PopoverTrigger asChild>
        <BjorkButton className="w-[230px] justify-start">
          <CalendarIcon aria-hidden="true" />
          {date ? format(date, "MMM d, yyyy") : "Pick a date"}
        </BjorkButton>
      </PopoverTrigger>
      <PopoverContent
        className={cn(bjorkMenu, "w-auto border-0 bg-transparent p-0 shadow-none")}
        align="center"
      >
        <BjorkCalendar mode="single" selected={date} onSelect={setDate} initialFocus />
      </PopoverContent>
    </Popover>
  );
}
components/ui/popover.tsx
"use client"

import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"

import { cn } from "@/lib/utils"

function Popover({
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
  return <PopoverPrimitive.Root data-slot="popover" {...props} />
}

function PopoverTrigger({
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}

function PopoverContent({
  className,
  align = "center",
  sideOffset = 4,
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
  return (
    <PopoverPrimitive.Portal>
      <PopoverPrimitive.Content
        data-slot="popover-content"
        align={align}
        sideOffset={sideOffset}
        className={cn(
          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
          className
        )}
        {...props}
      />
    </PopoverPrimitive.Portal>
  )
}

function PopoverAnchor({
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}

export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
components/bjork-ui/primitives/button.tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion, type HTMLMotionProps } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { useWebHaptics } from "web-haptics/react";
import type { HapticInput, TriggerOptions } from "web-haptics";
import { cn } from "@/lib/utils";

const bjorkButtonVariants = cva(
  "inline-flex shrink-0 transform-gpu items-center justify-center gap-2 whitespace-nowrap rounded-[13px] text-sm font-medium tracking-[-0.01em] outline-none backdrop-blur-md transition-[background-color,border-color,color,box-shadow,opacity] duration-150 ease-out focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-accent)]/45 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bjork-ring-offset)] disabled:pointer-events-none disabled:opacity-35 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default:
          "border border-transparent bg-[var(--bjork-field)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-surface)] hover:bg-[var(--bjork-surface-hover)]",
        secondary:
          "border border-[color:var(--bjork-border)] bg-[var(--bjork-surface-active)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)] hover:bg-[var(--bjork-surface-hover)]",
        outline:
          "border border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] text-[color:var(--bjork-text-strong)] shadow-[var(--bjork-shadow-soft)] hover:border-[color:var(--bjork-border-strong)] hover:bg-[var(--bjork-surface)] hover:text-[color:var(--bjork-text)]",
        ghost:
          "border border-transparent bg-transparent text-[color:var(--bjork-text-muted)] hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text)]",
        quiet:
          "border border-transparent bg-[var(--bjork-field-muted)] text-[color:var(--bjork-text-soft)] hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text-medium)]",
        accent:
          "bjork-layered-button-accent tracking-tight text-white",
        raised:
          "bjork-layered-button-raised tracking-tight",
      },
      size: {
        sm: "h-8 px-3 text-xs has-[>svg:first-child]:pl-2.5 has-[>svg:last-child]:pr-2.5",
        md: "h-[45px] px-4 has-[>svg:first-child]:pl-3.5 has-[>svg:last-child]:pr-3.5",
        lg: "h-12 px-5 text-[15px] has-[>svg:first-child]:pl-4.5 has-[>svg:last-child]:pr-4.5",
        icon: "size-[45px] p-0",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  },
);

export interface BjorkButtonProps
  extends Omit<HTMLMotionProps<"button">, "ref" | "size">,
    VariantProps<typeof bjorkButtonVariants> {
  haptics?: boolean;
  hapticPattern?: HapticInput;
  hapticOptions?: TriggerOptions;
}

export function BjorkButton({
  className,
  variant,
  size,
  disabled,
  haptics = true,
  hapticPattern = "light",
  hapticOptions,
  onPointerDown,
  tabIndex,
  ...props
}: BjorkButtonProps) {
  const shouldReduceMotion = useReducedMotion();
  const { trigger } = useWebHaptics();

  const handlePointerDown = React.useCallback(
    (event: React.PointerEvent<HTMLButtonElement>) => {
      if (!disabled && haptics) {
        void trigger(hapticPattern, hapticOptions);
      }

      onPointerDown?.(event);
    },
    [disabled, hapticOptions, hapticPattern, haptics, onPointerDown, trigger],
  );

  return (
    <motion.button
      data-slot="bjork-button"
      className={cn(bjorkButtonVariants({ variant, size, className }))}
      disabled={disabled}
      onPointerDown={handlePointerDown}
      whileHover={disabled || shouldReduceMotion ? undefined : { scale: 1.05 }}
      whileTap={disabled || shouldReduceMotion ? undefined : { scale: 0.95 }}
      transition={{ type: "spring", stiffness: 400, damping: 25 }}
      tabIndex={tabIndex ?? 0}
      {...props}
    />
  );
}

export { bjorkButtonVariants };
components/bjork-ui/primitives/calendar.tsx
"use client";

import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
import { cn } from "@/lib/utils";

type BjorkCalendarVariant = "outline" | "base";

type BjorkCalendarProps = React.ComponentProps<typeof Calendar> & {
  variant?: BjorkCalendarVariant;
};

const calendarVariants: Record<BjorkCalendarVariant, string> = {
  outline:
    "border-[color:var(--bjork-border)] bg-[var(--bjork-bg)] shadow-[var(--bjork-shadow-soft)]",
  base:
    "border-[color:var(--bjork-border-muted)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]",
};

export function BjorkCalendar({
  className,
  classNames,
  variant = "base",
  ...props
}: BjorkCalendarProps) {
  return (
    <Calendar
      className={cn(
        "rounded-[18px] border p-3 text-[color:var(--bjork-text)]",
        calendarVariants[variant],
        className,
      )}
      classNames={{
        caption_label: "text-sm font-medium text-[color:var(--bjork-text-strong)]",
        head_cell: "w-8 rounded-md text-[0.72rem] font-normal text-[color:var(--bjork-text-soft)]",
        cell: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:rounded-[10px]",
        day: "size-8 rounded-[10px] p-0 text-sm font-normal text-[color:var(--bjork-text-medium)] hover:bg-[var(--bjork-accent-soft)] hover:text-[color:var(--bjork-text)] focus:bg-[var(--bjork-accent-soft)]",
        day_selected:
          "bg-[var(--bjork-accent-fill)] text-[color:var(--bjork-accent-foreground)] hover:bg-[var(--bjork-accent-fill-hover)] hover:text-[color:var(--bjork-accent-foreground)] focus:bg-[var(--bjork-accent-fill-hover)] focus:text-[color:var(--bjork-accent-foreground)]",
        day_today: "bg-[var(--bjork-surface-active)] text-[color:var(--bjork-text)]",
        day_outside: "text-[color:var(--bjork-text-faint)] opacity-40",
        day_disabled: "text-[color:var(--bjork-text-faint)] opacity-70",
        nav_button:
          "inline-flex size-8 items-center justify-center rounded-full border-0 bg-transparent p-0 text-[color:var(--bjork-text-muted)] opacity-100 shadow-none hover:bg-transparent hover:text-[color:var(--bjork-text)] focus:bg-transparent",
        nav_button_previous: "absolute left-0",
        nav_button_next: "absolute right-0",
        ...classNames,
      }}
      {...props}
    />
  );
}
components/ui/calendar.tsx
"use client"

import * as React from "react"
import { AnimatePresence, motion, useReducedMotion } from "framer-motion"
import { ChevronLeft, ChevronRight } from "lucide-react"
import {
  CaptionLabel,
  DayContent,
  DayPicker,
  type CaptionLabelProps,
  type DayContentProps,
} from "react-day-picker"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"

function Calendar({
  className,
  classNames,
  components,
  month,
  defaultMonth,
  onMonthChange,
  showOutsideDays = true,
  ...props
}: React.ComponentProps<typeof DayPicker>) {
  const shouldReduceMotion = useReducedMotion()
  const [visibleMonth, setVisibleMonth] = React.useState(
    () => month ?? defaultMonth ?? new Date()
  )

  React.useEffect(() => {
    if (month) {
      setVisibleMonth(month)
    }
  }, [month])

  const handleMonthChange = React.useCallback(
    (nextMonth: Date) => {
      if (!month) {
        setVisibleMonth(nextMonth)
      }

      onMonthChange?.(nextMonth)
    },
    [month, onMonthChange]
  )

  const transition = React.useMemo(
    () =>
      shouldReduceMotion
        ? { duration: 0 }
        : { type: "spring" as const, stiffness: 360, damping: 34, mass: 0.7 },
    [shouldReduceMotion]
  )
  const contentTransition = React.useMemo(
    () =>
      shouldReduceMotion
        ? { duration: 0 }
        : { type: "spring" as const, stiffness: 420, damping: 30, mass: 0.55 },
    [shouldReduceMotion]
  )

  const CaptionLabelComponent = components?.CaptionLabel ?? CaptionLabel
  const DayContentComponent = components?.DayContent ?? DayContent

  const AnimatedCaptionLabel = React.useCallback(
    (captionProps: CaptionLabelProps) => (
      <div className="relative inline-grid min-w-24 place-items-center overflow-hidden">
        <AnimatePresence mode="popLayout" initial={false}>
          <motion.span
            key={`${captionProps.displayMonth.getFullYear()}-${captionProps.displayMonth.getMonth()}`}
            initial={
              shouldReduceMotion
                ? false
                : { opacity: 0, y: 5, filter: "blur(5px)" }
            }
            animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
            exit={
              shouldReduceMotion
                ? { opacity: 0 }
                : { opacity: 0, y: -5, filter: "blur(5px)" }
            }
            transition={contentTransition}
            className="col-start-1 row-start-1 transform-gpu"
          >
            <CaptionLabelComponent {...captionProps} />
          </motion.span>
        </AnimatePresence>
      </div>
    ),
    [CaptionLabelComponent, contentTransition, shouldReduceMotion]
  )

  const AnimatedDayContent = React.useCallback(
    (dayContentProps: DayContentProps) => (
      <span className="relative inline-grid size-full place-items-center overflow-hidden">
        <AnimatePresence mode="popLayout" initial={false}>
          <motion.span
            key={dayContentProps.date.getTime()}
            initial={
              shouldReduceMotion
                ? false
                : { opacity: 0, y: 6, scale: 0.96, filter: "blur(5px)" }
            }
            animate={{ opacity: 1, y: 0, scale: 1, filter: "blur(0px)" }}
            exit={
              shouldReduceMotion
                ? { opacity: 0 }
                : { opacity: 0, y: -6, scale: 0.96, filter: "blur(5px)" }
            }
            transition={contentTransition}
            className="col-start-1 row-start-1 transform-gpu"
          >
            <DayContentComponent {...dayContentProps} />
          </motion.span>
        </AnimatePresence>
      </span>
    ),
    [DayContentComponent, contentTransition, shouldReduceMotion]
  )

  return (
    <motion.div
      layout
      transition={transition}
      className="origin-top overflow-visible"
    >
      <DayPicker
        month={visibleMonth}
        defaultMonth={defaultMonth}
        onMonthChange={handleMonthChange}
        showOutsideDays={showOutsideDays}
        className={cn("p-3", className)}
        classNames={{
          months: "flex flex-col sm:flex-row gap-2",
          month: "flex flex-col gap-4",
          caption: "flex justify-center pt-1 relative items-center w-full",
          caption_label: "text-sm font-medium",
          nav: "flex items-center gap-1",
          nav_button: cn(
            buttonVariants({ variant: "outline" }),
            "size-7 bg-transparent p-0 opacity-50 hover:opacity-100"
          ),
          nav_button_previous: "absolute left-1",
          nav_button_next: "absolute right-1",
          table: "w-full border-collapse space-x-1",
          head_row: "flex",
          head_cell:
            "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
          row: "flex w-full mt-2",
          cell: cn(
            "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
            props.mode === "range"
              ? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
              : "[&:has([aria-selected])]:rounded-md"
          ),
          day: cn(
            buttonVariants({ variant: "ghost" }),
            "size-8 p-0 font-normal aria-selected:opacity-100"
          ),
          day_range_start:
            "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
          day_range_end:
            "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
          day_selected:
            "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
          day_today: "bg-accent text-accent-foreground",
          day_outside:
            "day-outside text-muted-foreground opacity-35 aria-selected:text-muted-foreground",
          day_disabled: "text-muted-foreground opacity-50",
          day_range_middle:
            "aria-selected:bg-accent aria-selected:text-accent-foreground",
          day_hidden: "invisible",
          ...classNames,
        }}
        components={{
          ...components,
          CaptionLabel: AnimatedCaptionLabel,
          DayContent: AnimatedDayContent,
          IconLeft: ({ className, ...props }) => (
            <ChevronLeft className={cn("size-4", className)} {...props} />
          ),
          IconRight: ({ className, ...props }) => (
            <ChevronRight className={cn("size-4", className)} {...props} />
          ),
        }}
        {...props}
      />
    </motion.div>
  )
}

export { Calendar }
components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
        destructive:
          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2 has-[>svg:first-child]:pl-3.5 has-[>svg:last-child]:pr-3.5",
        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg:first-child]:pl-2.5 has-[>svg:last-child]:pr-2.5",
        lg: "h-10 rounded-md px-6 has-[>svg:first-child]:pl-5 has-[>svg:last-child]:pr-5",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant,
  size,
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean
  }) {
  const Comp = asChild ? Slot : "button"

  return (
    <Comp
      data-slot="button"
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { Button, buttonVariants }
components/bjork-ui/primitives/_shared.ts
export const bjorkSurface =
  "border border-[color:var(--bjork-border)] bg-[var(--bjork-field)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-surface)]";

export const bjorkSoftSurface =
  "border border-[color:var(--bjork-border)] bg-[var(--bjork-surface-muted)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)]";

export const bjorkPanel =
  "rounded-[18px] border border-[color:var(--bjork-border)] bg-[var(--bjork-panel)] shadow-[var(--bjork-shadow-panel)]";

export const bjorkMenu =
  "z-50 rounded-[16px] border border-[color:var(--bjork-border)] bg-[var(--bjork-menu)] p-1 text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-menu)] backdrop-blur-xl";

export const bjorkMenuItem =
  "rounded-[12px] px-3 py-2 text-sm text-[color:var(--bjork-text-medium)] outline-none transition hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text)] focus:bg-[var(--bjork-surface-hover)] focus:text-[color:var(--bjork-text)] data-[highlighted]:bg-[var(--bjork-surface-hover)] data-[highlighted]:text-[color:var(--bjork-text)] data-[selected=true]:!bg-[var(--bjork-surface-hover)] data-[selected=true]:!text-[color:var(--bjork-text)] aria-selected:!bg-[var(--bjork-surface-hover)] aria-selected:!text-[color:var(--bjork-text)]";

More from bjork-ui

Browse all components in this library →