npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@daxence/flexy-date-picker

v1.0.2

Published

A fully customizable React date picker with single & range selection, injectable CSS theming, tooltips, and full event support.

Readme

flexy-date-picker

CI Release npm version npm downloads license React TypeScript Test coverage Conventional Commits PRs Welcome

A fully customizable React date & time picker with single / range selection, an optional time picker, injectable CSS theming, tooltips, and a complete event API. Built with TypeScript, Day.js, and zero hard-coded visual opinions — every color, radius, and slot can be overridden.

Desktop calendar

Table of contents


Features

  • ⚛️ React 19 + TypeScript, works with any React ≥ 18 host app
  • 📅 Day.js under the hood — no moment, no deprecated libs
  • 🔀 Single-date and date-range selection modes
  • ⏰ Optional built-in time picker (uniform step, custom ranges, or full per-date schedules) and a fully standalone TimePicker component
  • 🎨 CSS custom properties — inject any color/shape/spacing without a build step
  • 🧩 Per-slot classNames and styles props for surgical overrides (30+ slots)
  • 🌍 Locale & RTL-friendly via dayjs locales (fr, ar, …) and configurable week start
  • 📣 Full event API: onBeforeSelect, onAfterSelect, onDateChange, onRangeChange, onTimeChange, onMonthChange, onYearChange, onDayHover, onOpen, onClose, onClear
  • 💬 Tooltip support (string, ReactNode, or render-prop with rich per-day context)
  • 🖌️ Custom renderers for days, nav buttons, and the input trigger
  • 🧱 inline mode to embed the calendar directly in a layout (no popover)
  • ♿ Accessible by default: role="grid", aria-pressed, aria-disabled, aria-label, keyboard-navigable listboxes
  • 🚀 Vite library mode — ESM + UMD builds, tree-shakeable, fully typed

Installation

npm install @daxence/flexy-date-picker dayjs
pnpm add @daxence/flexy-date-picker dayjs

dayjs is a peer dependency and must be installed alongside the package. react and react-dom (≥ 18) are also required as peer dependencies.

Quick start

import { DatePicker } from '@daxence/flexy-date-picker';
import '@daxence/flexy-date-picker/styles'; // import the CSS once, anywhere in your app

function App() {
  return <DatePicker mode="single" onDateChange={(date) => console.log(date)} />;
}

Live examples

  • Demo app — run npm run dev for a playground covering every prop combination (booking calendars, day content injection, multilingual pickers, time schedules, custom themes, and more). Each demo section includes a "View code" toggle with the exact snippet used.
  • Storybook — run npm run storybook for isolated, documented stories per component (DatePicker, TimePicker, CalendarLegend), including controls for every prop.

Components

DatePicker

The main component. Supports single-date and range selection, an optional time picker, and full styling/theming control.

Selection & value props

| Prop | Type | Default | Description | |---|---|---|---| | mode | 'single' \| 'range' | 'single' | Selection mode | | value | Dayjs \| null | — | Controlled single value | | defaultValue | Dayjs \| null | — | Uncontrolled default | | rangeValue | { start, end } | — | Controlled range value | | defaultRangeValue | { start, end } | — | Uncontrolled range default |

Formatting & locale

| Prop | Type | Default | Description | |---|---|---|---| | format | string | 'MMM D, YYYY' | dayjs format string | | locale | string | — | dayjs locale (e.g. 'fr', 'ar') | | lang | string | — | Alias for locale | | weekStartsOn | 0–6 | 0 (Sunday) | First day of week |

Calendar display

| Prop | Type | Default | Description | |---|---|---|---| | numberOfMonths | number | 1 | Months displayed at once | | showMonthYearSelectors | boolean | true | Show month/year dropdown selectors | | showAdjacentMonthDays | boolean | false | Show adjacent-month days in the grid | | showOutsideDays | boolean | — | Deprecated alias for showAdjacentMonthDays | | highlightToday | boolean | true | Highlight today | | inline | boolean | false | Render the calendar directly, without an input/popover | | placement | 'top' \| 'bottom' \| 'auto' | 'auto' | Popover placement |

Constraints & state

| Prop | Type | Default | Description | |---|---|---|---| | minDate / maxDate | Dayjs | — | Earliest / latest selectable date | | disabled | DisabledConfig \| boolean | — | Disable dates (before, after, dates, predicate) | | isDisabled | boolean | false | Disable the whole picker | | readOnly | boolean | false | Read-only mode | | placeholder | string \| [string, string] | — | Input placeholder(s) | | clearable | boolean | true | Show the clear button |

Time picker (built into DatePicker)

| Prop | Type | Default | Description | |---|---|---|---| | enableTime | boolean | false | Show a time slot list alongside the calendar | | timeStep | number | 15 | Uniform slot interval in minutes | | timeFormat | string | 'HH:mm' | dayjs format for time labels | | minTime / maxTime | string (HH:mm) | '00:00' / '23:59' | Bounds for generated slots | | disabledTime | DisabledTimeConfig | — | Disable slots by weekday, date, or predicate | | timeSchedule | Record<'YYYY-MM-DD', DateSchedule> | — | Per-date schedule override (multiple ranges, custom steps, unavailability) | | defaultTimeSchedule | DateSchedule | — | Fallback schedule for dates not present in timeSchedule |

Styling

| Prop | Type | Description | |---|---|---| | theme | DatePickerTheme | CSS variable overrides (see theming) | | classNames | DatePickerClassNames | Per-slot className overrides (see slots) | | styles | DatePickerStyles | Per-slot inline style overrides | | className / style | string / CSSProperties | Extra class/style on the root element |

Custom renderers

| Prop | Signature | Description | |---|---|---| | renderDay | (date: Dayjs, props: TooltipRenderProps) => ReactNode | Fully custom day cell content | | renderNavButton | (direction: 'prev' \| 'next', onClick: () => void) => ReactNode | Custom prev/next month buttons | | renderInput | (props: RenderInputProps) => ReactNode | Fully custom trigger input |

Tooltip

| Prop | Type | Default | Description | |---|---|---|---| | tooltip | string \| ReactNode \| (props: TooltipRenderProps) => ReactNode | — | Day tooltip content | | tooltipDelay | number | 300 | Tooltip delay (ms) |

Events

| Prop | Signature | Description | |---|---|---| | onDateChange | (date: Dayjs \| null) => void | Fires on single-date selection/clear | | onRangeChange | (range: { start, end }) => void | Fires on range selection/clear | | onTimeChange | (date: Dayjs \| null, time: string) => void | Fires when a time slot is picked | | onBeforeSelect | (date: Dayjs, meta: DateSelectionMeta) => boolean \| void | Return false to cancel a selection | | onAfterSelect | (date: Dayjs, meta: DateSelectionMeta) => void | Fires right after a selection is committed | | onMonthChange | (month: number, year: number) => void | Fires when the visible month changes | | onYearChange | (year: number) => void | Fires when the visible year changes | | onDayHover | (date: Dayjs \| null) => void | Fires on day hover (range preview) | | onOpen / onClose | () => void | Popover open/close | | onClear | () => void | Fires when the clear button is used |


TimePicker (standalone)

A self-contained time-of-day picker that can be used independently of DatePicker — as a popover-triggering input or rendered inline.

import { TimePicker } from '@daxence/flexy-date-picker';

<TimePicker
  value={time}
  onChange={setTime}
  timeStep={15}
  minTime="08:00"
  maxTime="18:00"
/>

| Prop | Type | Default | Description | |---|---|---|---| | value | string (HH:mm) | — | Controlled value | | defaultValue | string | — | Uncontrolled initial value | | onChange | (time: string) => void | — | Called with the selected HH:mm, or '' after clear | | timeStep | number | 15 | Uniform slot interval in minutes (ignored when schedule is set) | | minTime / maxTime | string (HH:mm) | '00:00' / '23:59' | Slot bounds (ignored when schedule is set) | | schedule | DateSchedule | — | Schedule-based generation: multiple time ranges, each with its own step, with gaps between them producing no slots. Overrides timeStep/minTime/maxTime | | timeFormat | string | 'HH:mm' | dayjs format for slot labels and display | | placeholder | string | — | Placeholder text when no value is selected | | locale | string | — | dayjs locale | | date | Dayjs | today | Reference date used by disabledTime | | disabledTime | DisabledTimeConfig | — | Disable slots by weekday, date, or predicate | | inline | boolean | false | Render the slot list directly, without an input/popover | | isDisabled | boolean | false | Disable all interaction | | readOnly | boolean | false | Show the value but prevent changes | | clearable | boolean | true | Show the clear button | | onOpen / onClose / onClear | () => void | — | Popover lifecycle events | | className / style | string / CSSProperties | — | Root element overrides |


CalendarLegend

A small presentational component for labeling calendar states (e.g. availability legends in booking UIs).

import { CalendarLegend } from '@daxence/flexy-date-picker';

<CalendarLegend
  items={[
    { label: 'Available', className: 'fldp-legend-box--available' },
    { label: 'Selected', className: 'fldp-legend-box--selected' },
    { label: 'Unavailable', className: 'fldp-legend-box--unavailable' },
  ]}
/>

| Prop | Type | Default | Description | |---|---|---|---| | items | { label: string \| ReactNode; className: string }[] | 5 default French labels | Legend rows, each rendered as a colored box + label | | className | string | — | Extra class on the root element | | style | CSSProperties | — | Extra inline style on the root element |


Theming via CSS custom properties

Override any token on a parent element:

.my-app {
  --fldp-primary: #7c3aed;
  --fldp-primary-hover: #6d28d9;
  --fldp-muted: #ede9fe;
  --fldp-border-radius: 12px;
  --fldp-day-border-radius: 50%;
  --fldp-font-family: 'Inter', sans-serif;
}

Or pass the theme prop:

<DatePicker
  lang="fr"
  weekStartsOn={1}
  theme={{
    primary: '#7c3aed',
    primaryHover: '#6d28d9',
    muted: '#ede9fe',
    borderRadius: '12px',
    dayBorderRadius: '50%',
  }}
/>

All available tokens

| Token | Default | Description | |---|---|---| | --fldp-primary | #3b82f6 | Accent / selected color | | --fldp-primary-hover | #2563eb | Accent hover | | --fldp-primary-foreground | #ffffff | Text on accent background | | --fldp-background | #ffffff | Popover background | | --fldp-surface | rgba(255, 255, 255, 0.88) | Trigger + menu surface | | --fldp-surface-strong | rgba(255, 255, 255, 0.98) | Strong surface tone | | --fldp-foreground | #0f172a | Default text color | | --fldp-muted | #eff6ff | In-range highlight | | --fldp-accent-soft | rgba(15, 118, 110, 0.12) | Soft hover/accent state | | --fldp-muted-foreground | #64748b | Placeholder / weekday text | | --fldp-border | #e2e8f0 | Border color | | --fldp-input-background | #ffffff | Input background | | --fldp-today-color | #3b82f6 | Today dot + text | | --fldp-border-radius | 8px | Outer radius | | --fldp-day-border-radius | 6px | Day cell radius | | --fldp-shadow | | Popover box shadow | | --fldp-font-family | system-ui | Font family | | --fldp-font-size | 14px | Base font size | | --fldp-disabled-opacity | 0.38 | Disabled element opacity | | --fldp-transition-duration | 150ms | Animation speed | | --fldp-z-index | 9999 | Popover z-index |


Per-slot class / style injection

Every visual slot accepts a className and inline style:

<DatePicker
  classNames={{
    calendar: 'my-calendar',
    daySelected: 'my-selected-day',
    popover: 'my-popover',
  }}
  styles={{
    popover: { background: '#fefce8' },
    dayInRange: { fontWeight: 700 },
  }}
/>

Available slots: root, inputWrapper, input, separator, clearButton, popover, calendar, calendarHeader, navButton, monthYearLabel, weekdays, weekday, daysGrid, day, dayToday, daySelected, dayRangeStart, dayRangeEnd, dayInRange, dayDisabled, dayOutside, dayHovered, timePicker, timeTitle, timeSlotList, timeSlot, timeSlotSelected, timeSlotDisabled, tooltip.


Tooltips

// Static string
<DatePicker tooltip="Click to select" />

// ReactNode
<DatePicker tooltip={<strong>Pick a date</strong>} />

// Render prop — full control, including hotel-style "X nights" hints
<DatePicker
  mode="range"
  tooltip={({ date, isToday, isDisabled, isRangeStart, isRangeEnd, isInRange, nights }) => {
    if (isDisabled) return '🚫 Unavailable';
    if (nights && isRangeEnd) return `${nights} night${nights > 1 ? 's' : ''}`;
    if (isRangeStart) return '📌 Check-in';
    if (isRangeEnd) return '🏁 Check-out';
    if (isToday) return '📅 Today';
    return date.format('dddd, MMM D');
  }}
  tooltipDelay={200}
/>

Disabled dates

<DatePicker
  disabled={{
    before: dayjs('2024-01-01'),   // disable all before Jan 1
    after: dayjs('2024-12-31'),    // disable all after Dec 31
    dates: [dayjs('2024-06-15')],  // specific dates
    predicate: (d) => d.day() === 0 || d.day() === 6, // weekends
  }}
/>

Date + time picker

Combine calendar selection with a time slot list in one component:

<DatePicker
  mode="single"
  value={meetingDate}
  onDateChange={setMeetingDate}
  onTimeChange={(date, time) => console.log(date, time)}
  enableTime
  timeStep={15}
  timeFormat="HH:mm"
  minTime="08:00"
  maxTime="20:00"
  disabledTime={{
    weekdays: { 0: ['08:00', '08:15', '08:30'], 6: ['08:00', '08:15', '08:30'] },
  }}
/>

Per-date time schedules

Give individual dates their own set of time ranges — with gaps, different step intervals, or full unavailability — via timeSchedule / defaultTimeSchedule:

<DatePicker
  mode="single"
  enableTime
  defaultTimeSchedule={{ ranges: [{ from: '09:00', to: '17:00', step: 30 }] }}
  timeSchedule={{
    '2026-06-15': { unavailable: true }, // fully booked, no slots shown
    '2026-06-16': {
      ranges: [
        { from: '08:00', to: '12:00', step: 15 },
        { from: '14:00', to: '16:00', step: 5 },
        { from: '16:00', to: '18:00', step: 10 },
      ],
    },
  }}
/>

Range picker

const [range, setRange] = useState({ start: null, end: null });

<DatePicker
  mode="range"
  rangeValue={range}
  onRangeChange={setRange}
  numberOfMonths={2}
/>

Custom renderers

<DatePicker
  renderDay={(date, { isSelected }) => (
    <span style={{ fontWeight: isSelected ? 700 : 400 }}>{date.date()}</span>
  )}
  renderNavButton={(direction, onClick) => (
    <button onClick={onClick}>{direction === 'prev' ? '←' : '→'}</button>
  )}
  renderInput={({ value, onClick, isOpen }) => (
    <button onClick={onClick}>{value || 'Pick a date'} {isOpen ? '▲' : '▼'}</button>
  )}
/>

Accessibility

The calendar grid, navigation, and inputs follow WAI-ARIA authoring practices out of the box:

  • Day grid uses role="grid" / role="row" / role="columnheader", with each day exposing aria-label (full localized date), aria-pressed (selected state), and aria-disabled.
  • Month/year navigation exposes aria-label="Previous month" / "Next month", and the month/year dropdowns use role="listbox" / role="option" with aria-expanded / aria-selected.
  • The input trigger uses role="button", aria-haspopup="dialog", and aria-expanded; the popover itself uses role="dialog".
  • The standalone time picker region is exposed via role="region" with a descriptive aria-label.
  • Decorative icons/carets are marked aria-hidden="true" so they're skipped by screen readers.

Quality & testing

This project is built and gated the way a production library should be:

| Check | Status | |---|---| | Unit test coverage | 100% statements / branches / functions / lines, enforced as a hard CI gate (vitest --coverage, thresholds in vite.config.ts) | | Test suite | 100+ tests across every component, context, and utility (vitest, React Testing Library) | | Type safety | strict TypeScript, zero any leaking through the public API, full .d.ts output | | Linting | ESLint with typescript-eslint, react-hooks, jsx-a11y, and unicorn rule sets, zero warnings allowed | | Accessibility | WAI-ARIA roles/attributes on every interactive element (see Accessibility) and an automated @storybook/addon-a11y check on every story | | Bundle | Tree-shakeable ESM + UMD output via vite-plugin-dts, ~35 kB gzipped for the full library | | Commits & releases | Conventional Commits enforced via commitlint + husky, versioning and changelog automated with semantic-release |

Every prop, event, and edge case documented above (disabled dates, per-date time schedules, range reordering, keyboard navigation, placement auto-resolution, etc.) is backed by a dedicated test — the goal is that the README and the test suite never drift apart.

npm run test            # Run the full suite
npm run test:coverage   # Run with the 100% coverage gate enforced
npm run lint             # Zero-warning lint pass

Development

npm install
npm run dev              # Vite dev server with the demo app (with copy-pasteable code snippets)
npm run build             # Build the library to /dist
npm run test               # Run the test suite
npm run test:coverage      # Run tests with coverage
npm run storybook          # Explore components interactively
npm run build-storybook    # Build a static Storybook site
npm run lint               # Lint the codebase

Contributing

Issues and pull requests are welcome. Please:

  1. Keep the codebase feature-based (src/features/<feature>/...) and add tests + Storybook stories for new behavior.
  2. Run npm run lint and npm run test before opening a PR.
  3. Follow Conventional Commits — releases are automated with semantic-release.

License

MIT © Daxence Solutions