Skip to content

Date Range Picker

A refined range picker: a trigger field that reads the range, a popover with two months side by side, a presets rail with a sliding highlight, a continuous painted band with a live hover preview, and a full keyboard grid.

DOM + CSS0 deps1 file · 26 KB TSXreduced-motion aware

Prominimalglass
Preview

Props

Show presets
Disable days before today

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
import type { CSSProperties, KeyboardEvent as ReactKeyboardEvent } from "react";

export type DateRangeValue = { start: string | null; end: string | null };

export type DateRangePickerProps = {
  /** Show the presets rail (Today, Last 7 days, ...). */
  showPresets?: boolean;
  /** Render with the popover already open. */
  defaultOpen?: boolean;
  /** "YYYY-MM-DD". Pins "today" (SSR-stable); defaults to the visitor's local date. */
  today?: string;
  /** Controlled value, "YYYY-MM-DD" strings. */
  value?: DateRangeValue;
  /** Initial value when uncontrolled. */
  defaultValue?: DateRangeValue;

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this component

A trigger field reads the chosen range (Oct 6 - Oct 21, 2026, 15 nights) and opens a popover with two months side by side, or one when its own container is narrow. The selection is a three-phase state machine (nothing, start only, full range): the first click sets the start, the second sets the end (swapping if it came first), a third begins again. While the end is pending, the band to the hovered or focused day is painted from a separate hover value and never written into the range, so moving away reverts cleanly. Days are whole numbers since 1970 (UTC), so there is no time-zone or daylight-saving drift, and today is read through useSyncExternalStore with a fixed server snapshot, so the markup is hydration-safe; pass today to pin it. The band is one continuous strip with rounded ends that wraps at week and month edges; the endpoints are lit discs. A presets rail (Today, Last 7 days, Last 30 days, This month, Custom) drives the same draft, and one highlight slides between them on a 320ms ease. Months slide in the direction you travel in 240ms. min and max dim and strike through unavailable days, and the presets clamp to them. Apply commits to onChange and the trigger; Cancel, Escape or a click outside discards the draft.

Curator’s note

Rebuilt in October as a proper popover picker: two months, a presets rail with a sliding highlight, a painted band and a real keyboard grid. One file, no dependencies, no date library.

Pairs well with

Pro