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

react-locale-datepicker

v0.5.1

Published

A React date picker that localizes itself from the Intl API — every locale, no locale files, RTL-aware.

Readme

react-locale-datepicker

A React date picker that localizes itself from the Intl API — every locale the browser knows, no locale files to register, right-to-left aware, zero runtime dependencies beyond React.

npm CI license

Live demo — locale switcher (including RTL), disabled days, themes and dark mode.

Interaction demo: typing masks into the date format, Enter commits with a long-form echo, a disabled weekend click is blocked, month view navigation, one-tap commit

| Light | Dark | RTL (Arabic) | | --- | --- | --- | | Light theme | Dark theme | RTL Arabic |

| Month view | Year view | Disabled days | | --- | --- | --- | | Month selection grid | Year selection grid | Weekends disabled via shouldDisableDate |

| Custom theme (tokens) | Japanese | Error state | | --- | --- | --- | | Violet theme via rldp custom properties | Japanese locale | Error state border |

All captured from the real component by scripts/capture-screenshots.mjs (npm run screenshots), including a 320 px variant at docs/assets/picker-mobile.png.

Install

npm install react-locale-datepicker
# or: pnpm add react-locale-datepicker
# or: yarn add react-locale-datepicker

Peers: react and react-dom >=18.

Quick start

import { useState } from "react";
import { LocaleDatePicker } from "react-locale-datepicker";
import "react-locale-datepicker/styles.css";

export function Example() {
  const [value, setValue] = useState<Date | null>(null);

  return (
    <LocaleDatePicker
      value={value}
      onChange={setValue}
      locale="de"
      placeholder="dd.mm.yyyy"
      aria-label="Appointment date"
    />
  );
}

Import the stylesheet once (app root or the module that mounts the picker). Without it the component is intentionally unstyled — that is the headless escape hatch.

CommonJS

const { LocaleDatePicker, resolveLocale } = require("react-locale-datepicker");
require("react-locale-datepicker/styles.css");

Why another date picker

Most React date pickers ask you to import and register a locale bundle per language. That is fine for two or three languages and painful for thirty. This one derives month names, weekday names, the first day of the week and the long-form date echo from Intl.DateTimeFormat at runtime, so adding a language means passing a different string.

  • Localization with no locale files. Names, week start and echo from Intl.
  • RTL by construction. Arabic and Hebrew lay out correctly.
  • One tap to select. Clicking a day commits and closes — no confirm step.
  • Business-timezone "today". timeZone="America/New_York" (or a today Date) anchors the today marker and default view to the seller's calendar day; todayInTimeZone is exported for matching shouldDisableDate rules. Values stay local-midnight Dates — never converted.
  • Typing that survives mobile. Digits mask into dd.MM.yyyy; localized digits normalize to ASCII for every numbering system Intl knows; separators (. , / -, Arabic and ideographic commas) close and pad the segment; the open calendar follows a fully typed date live.
  • Readable echo. The committed date is restated in words under the field.
  • Timezone-safe values. Local-midnight Date objects — never a silent day-shift from a UTC round trip.
  • Caller-owned disabled days. shouldDisableDate is the single authority.
  • Accessible. Keyboard map (arrows, Page/Shift+Page, Home/End, Enter, Escape), ARIA pass-through, focus returns to the input on close.
  • Self-contained CSS. --rldp-* tokens, light/dark (OS + class/attribute), classNames / icons overrides. Zero runtime dependencies.

Props (essentials)

| Prop | Type | Notes | | --- | --- | --- | | value | Date \| null | Local midnight. Read with getDate / getMonth / getFullYear. | | onChange | (date: Date \| null) => void | Fires on commit, not every keystroke. | | locale | string | Any BCP 47 tag. Non-standard aliases are normalized (e.g. uauk). | | placeholder | string | Required. Display format is fixed dd.MM.yyyy; supply a matching hint. | | shouldDisableDate | (date: Date) => boolean | Sole authority on selectability. | | minDate / maxDate | Date \| null | Bound month/year navigation only — do not override the predicate. | | defaultCalendarMonth | Date \| null | Month shown when opening with no value. | | timeZone | string | IANA zone "today" is derived in (seller's calendar day). Never converts the value. | | today | Date | Inject "today" outright — wins over timeZone. | | disabled | boolean | | | hasError | boolean | Visual only. | | showEcho / showWeekdayHeader / showTodayMarker | boolean | Opt out of a built-in. All default to true — today's behaviour. | | onBlur | (current: Date \| null) => void | Receives the just-committed value. | | onDisabledOpenAttempt | () => void | Fires when a disabled picker is tapped. | | onValidationError | (reason) => void | Why a typed entry did not commit: "missing", "impossible-date", "not-selectable". | | aria-label / aria-invalid / aria-describedby | | Pass through to the input. | | className | string | Root element. | | themeName | "default" \| "minimal" \| "soft" \| "high-contrast" | Selects a shipped theme. Unset inherits an ancestor's. | | classNames | Partial<Record<Slot, string>> | Per-slot class overrides (appended after built-ins). | | styles | Partial<Record<Slot, CSSProperties>> | Per-slot inline styles, same keys. | | labels | Partial<Labels> | Strings Intl cannot supply — English defaults; override for non-English UIs. See below. | | icons | Partial<Record<IconName, ReactNode>> | Substitute calendar / chevron glyphs. | | portal | boolean \| HTMLElement | Opt-in escape from overflow: hidden ancestors. Default false keeps the 0.3.x in-tree popover. | | manualEntryOnTouch | "second-tap" \| "immediate" | When the on-screen keyboard may appear, touch only. Default "second-tap". See below. |

Full contract: docs/API.md.

The on-screen keyboard (touch)

On a phone the keyboard costs roughly half the viewport, and most taps on a date field only ever wanted the grid. Since 0.5.0 the default is:

tap the field   → calendar opens, no keyboard
tap the text again (calendar open) → keyboard: deliberate manual entry
tap the icon    → calendar opens, no keyboard
pick a day      → closes, no keyboard

The field carries inputMode="none" until that second tap. Typing is never removedinputMode governs only the virtual keyboard, so hardware keyboards, paste and every a11y affordance keep working, and a fine pointer is unaffected entirely. Pointer type is read live from (pointer: coarse), so a hybrid device is judged per interaction, not once at mount.

Pass manualEntryOnTouch="immediate" for the pre-0.5 behaviour, where any tap on the field raises the keyboard.

Two related behaviours are not optional, because both were plain defects: picking a day never returns focus to the input on touch (it did, and that raised the keyboard on the tap meant to finish the job — keyboard and assistive-tech activation still get focus back), and the popover's above/below decision is made once per open and frozen, so a viewport shrinking under the keyboard cannot flip a calendar the visitor is reading over the top of the field.

Locale helper

import { resolveLocale } from "react-locale-datepicker";

resolveLocale("ua"); // "uk" — safe for your own Intl calls
resolveLocale("en_US"); // "en" — malformed tags fall back instead of throwing

Never pass a raw caller-supplied locale into Intl without this (or equivalent) normalization: a bad tag throws and can take down a whole React island.

Labels — what you must translate

Month names, weekday names, the long-form echo, day accessible names, and the previous / next navigation labels come from Intl for the locale you pass. When labels.previousMonth / labels.nextMonth are omitted, the nav buttons are named with the month and year they navigate to (e.g. "серпень 2026" under locale="uk") — not a static "Previous month" string. You do not need to translate those two.

The other four defaults are English and will ship English into a non-English page unless you override them:

| Key | Default | When it is heard | | --- | --- | --- | | keyboardHelp | "Use the arrow keys…" | Once, when keyboard focus first enters the day grid | | openCalendar | "Open calendar" | Trigger name while empty | | changeDate | "Change date" | Trigger name prefix while a date is committed | | closeCalendar | "Close calendar" | Trigger name while the calendar is open |

Worked example for a Ukrainian UI (the rest of the calendar still follows locale="uk" / locale="ua" via Intl):

<LocaleDatePicker
  value={value}
  onChange={setValue}
  locale="ua"
  placeholder="дд.мм.рррр"
  aria-label="Дата початку подорожі"
  labels={{
    keyboardHelp:
      "Клавіші зі стрілками — між днями, Page Up/Down — місяць, Enter — вибір.",
    openCalendar: "Відкрити календар",
    changeDate: "Змінити дату",
    closeCalendar: "Закрити календар",
  }}
/>

Do not invent machine translations inside the package — the consumer owns these four strings across their locales.

Popover inside overflow: hidden

The default popover is position: absolute inside the component root. A card shell like overflow-hidden rounded-2xl clips it — verified in the browser matrix. Pass portal to escape:

// Portal to document.body with position:fixed coordinates.
<LocaleDatePicker portal /* ... */ />

// Or into a host you already use for modals / stacking.
<LocaleDatePicker portal={modalRootEl} /* ... */ />

Default stays in-tree so existing layouts do not reflow. Keyboard model, Escape-to-close and outside-click close keep working with either mode. Inside a cross-origin iframe the portal targets the iframe's document (the only document the script can reach).

Constraints example

<LocaleDatePicker
  value={value}
  onChange={setValue}
  locale="en"
  placeholder="dd.mm.yyyy"
  minDate={new Date(2026, 0, 1)}
  maxDate={new Date(2026, 11, 31)}
  shouldDisableDate={(d) => d.getDay() === 0 || d.getDay() === 6}
  aria-label="Appointment date"
/>

Theming

Full guide: docs/THEMING.md.

Import the stylesheet, then override tokens anywhere up the tree — on an ancestor, or on the root via className. The nearest declaration wins.

.my-form {
  --rldp-accent: oklch(0.55 0.18 250);
  --rldp-radius: 0.5rem;
}

Four themes ship — default, minimal, soft, high-contrast — selectable from React or from CSS alone, and they nest:

<LocaleDatePicker themeName="soft" /* ... */ />
<div data-rldp-theme="high-contrast">…</div>

Dark mode is CSS-only:

  • follows the OS via color-scheme + light-dark();
  • override with a .dark / .light class or [data-theme="dark|light"] on an ancestor (compatible with next-themes and similar).

Tailwind v4

Tailwind v4's @theme reads plain CSS variables, so one block bridges the two in either direction. Nothing Tailwind-specific ships in the package.

@import "tailwindcss";
@import "react-locale-datepicker/styles.css";

@theme inline {
  --color-rldp-accent: var(--rldp-accent); /* picker tokens -> utilities */
}

.my-form {
  --rldp-accent: var(--color-indigo-600); /* your palette -> the picker */
  --rldp-radius: var(--radius-lg);
}

Slot overrides, per docs/ANATOMY.md:

<LocaleDatePicker
  classNames={{
    input: "my-input",
    daySelected: "my-selected-day",
    echo: "my-echo",
  }}
  styles={{ popover: { borderRadius: 16 } }}
  /* ... */
/>

Tailwind preflight beats the package's own styling

The stylesheet is built so consumer CSS always wins: every rule sits in the rldp cascade layer and is written with :where(). Both halves work exactly as designed. The half that is easy to miss is what "consumer CSS" includes — Tailwind's preflight is unlayered consumer CSS, so preflight wins too, on every property it resets.

Two independent barriers, and preflight clears both:

  • Cascade layer. Unlayered CSS beats any cascade layer regardless of selector. In Tailwind v3 preflight is emitted unlayered, so it outranks @layer rldp even where the package rule is more specific. Layer order is decided before specificity is ever consulted.
  • Specificity. :where(…) scores 0,0,0, so preflight's button, input, … at 0,0,1 would win on specificity as well.

The rules that collide, from Tailwind's preflight.css:

| Preflight rule | Effect on the picker | | --- | --- | | button, input, … { color: inherit } | The field and every day cell take the host's text colour. --rldp-foreground and --rldp-disabled-foreground never land. | | button, input, … { font-size: 100% } | The field renders at the browser default, not the picker's. | | button { background-color: transparent } | A themed field background disappears. | | *, ::before, ::after { border-width: 0 } | The field can lose its border entirely. |

None of this throws, and none of it looks wrong in a default light theme in English — which is exactly why it reaches production. Three real ways it has:

  • a dark-mode visitor got a dark field with the host's near-black text on it, i.e. the date they had just typed, invisible;
  • disabled days rendered pixel-identical to selectable ones, because --rldp-disabled-foreground never applied — a click on a closed date simply did nothing, with no visual reason why;
  • the field lost its border and read as a different control from the inputs beside it.

The rule to work from: anything this package styles on a <button> or an <input> needs an explicit class from you. That is what classNames is for, and a Tailwind utility is unlayered too, so it lands where the package cannot:

<LocaleDatePicker
  classNames={{
    field: "border-2 border-gray-200 bg-white",
    input: "text-base text-gray-900",
    dayDisabled: "text-gray-400 cursor-not-allowed",
  }}
  /* ... */
/>

Reaching the same result without touching the component: put Tailwind in a layer of its own and order it under rldp. Tailwind v4 does this natively — @import "tailwindcss" layer(tw); then @layer tw, rldp;. Under v3 the equivalent is wrapping @tailwind base in a layer via PostCSS. Either way the package stops being outranked, which is the fix if you would rather not repeat host styling in classNames.

Doing that ordering for the consumer is on the roadmap (docs/ROADMAP.md); until then the table above is the contract.

Keyboard

| Key | Action | | --- | --- | | Arrow keys | Move by day / week (RTL-aware) | | PageUp / PageDown | Previous / next month | | Shift+PageUp / PageDown | Previous / next year | | Home / End | Start / end of locale week | | Enter / Space | Commit focused day | | Escape | Close and return focus to the input | | ArrowDown (from input) | Open, then enter the grid |

Display format

Today the typed/display format is fixed dd.MM.yyyy. A locale-derived or custom format contract is on the roadmap (docs/ROADMAP.md). Always set placeholder to match.

Browser support

Modern evergreen browsers with Intl.DateTimeFormat and Intl.Locale week info (Chrome, Firefox, Safari, Edge). Tested in CI across Chromium, Firefox and WebKit at 320 / 768 / 1280 px.

Development

npm install
npm run check          # typecheck + lint + unit tests
npm run test:tz        # unit suite under UTC, America/Los_Angeles, Asia/Tokyo, Asia/Kathmandu
npm run test:e2e       # Playwright matrix
npm run build          # tsup → dist/ (ESM + CJS + d.ts + styles.css)

Demo

examples/ is a small Vite app consuming the built package, with a locale switcher (including RTL), disabled days and theme toggles. It resolves dist/, so build the package first:

npm run build && cd examples && npm install && npm run dev

Documentation

| Document | What it covers | | --- | --- | | docs/API.md | Full public API and contracts | | docs/ANATOMY.md | Published parts, slots and state attributes | | docs/THEMING.md | Tokens, named themes, dark mode, Tailwind bridge | | docs/PLAN.md | Implementation plan and status | | docs/DECISIONS.md | Design decisions | | docs/EXTRACTION.md | Parity contract (must not regress) | | docs/TESTING.md | Required test matrix | | docs/ROADMAP.md | Feature and theming roadmap | | docs/RELEASING.md | Versioning and release process | | CHANGELOG.md | What changed in each release |

Contributing and support

Maintained as time allows: issues and pull requests are welcome, and there is no response-time promise. Start with CONTRIBUTING.md — the most valuable reports are locale rendering problems, day-shift timezone bugs and screen-reader findings. Security reports go through SECURITY.md, never a public issue.

License

MIT © 2026 Yevhen Salitrynskyi