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

@rxova/react-date-input

v1.0.3

Published

A segmented, keyboard-first date field with no calendar and no date library. Headless, zero-dependency, locale-aware through Intl.

Downloads

249

Readme

A segmented date field with no calendar, no date library, and no timezone bugs. Headless, zero-dependency, locale-aware through Intl.

npm install @rxova/react-date-input

📖 Documentation & live examples → — guides, form recipes, theming, locale handling, and migration from another date picker.

  • No calendar. For a birthday or an invoice date, typing 15/03/1999 is faster than eleven clicks through a month grid. This is the field for dates you already know.
  • No date library. Segment order, separators and month names come from Intl, which every engine already ships. react-datepicker brings date-fns and @floating-ui and lands at 40.0 kB brotli; react-day-picker at 17.9 kB. This is 3.3 kB and brings nothing.
  • No Date objects, so no off-by-one. The value is a YYYY-MM-DD string from end to end.
  • Fully keyboard-operable — type, arrow, Home/End, auto-advance, backspace.
  • Real spinbutton semantics per segment, with the month announced by name.
  • Zero runtime dependencies, 3.3 kB brotli, no stylesheet to import.

Basic use

import { useState } from "react";
import { DateInput } from "@rxova/react-date-input";

function Birthday() {
  const [value, setValue] = useState<string | null>(null);
  return <DateInput label="Date of birth" value={value} onChange={setValue} max="2026-07-29" />;
}

value is '1999-03-15' or null. onChange fires when the date becomes complete and valid, and when it stops being — never with a half-typed date in between.

Why a string and not a Date

This is the design decision everything else follows from.

// Somewhere west of Greenwich:
new Date("2026-03-01").getDate(); // 28  — parsed as UTC midnight
new Date(2026, 2, 1).getDate(); // 1   — parsed as local midnight

A calendar date is a year, a month and a day. It is not a point in time. The moment it becomes one it acquires a timezone it never had, and that single discrepancy is behind most "my date picker is a day off" reports. So this package never constructs a Date for a value: it stores three numbers, formats them as YYYY-MM-DD, and compares ranges as strings — which works because that format sorts lexicographically in exactly the same order it sorts chronologically.

If you need a Date at the boundary of your app, construct it there and be explicit about the zone you mean.

Locales

Segment order, separators and month names all come from Intl.DateTimeFormat, so this is correct for every locale the platform knows and costs nothing:

import { DateInput } from "@rxova/react-date-input";

function Examples() {
  return (
    <>
      <DateInput label="US" locale="en-US" /> {/* mm / dd / yyyy */}
      <DateInput label="UK" locale="en-GB" /> {/* dd / mm / yyyy */}
      <DateInput label="Japan" locale="ja-JP" /> {/* yyyy / mm / dd */}
    </>
  );
}

Omit locale to use the runtime's own. A malformed tag (en_US, with an underscore) falls back to ISO order and reports locale-invalid through onWarn rather than throwing.

Keyboard

Every part of the field is reachable and operable without a pointer.

| Key | Effect | | ---------------------- | ----------------------------------------------------------------------------- | | 0–9 | Type into the focused segment; advances as soon as no further digit could fit | | ↑ / ↓ | Step the focused segment, wrapping at its ends | | ← / → | Move between segments; stops at the ends rather than cycling | | Home / End | Jump the focused segment to its minimum or maximum | | Backspace / Delete | Clear the focused segment | | Tab | Move to the next segment, then out of the field |

Typing is forgiving in the ways that matter: 4 in a day is immediately the 4th, 1 waits to see whether you meant the 1st or the 19th, and 1 then 9 in a month gives you September rather than rejecting the keystroke.

Range

import { DateInput } from "@rxova/react-date-input";

function Booking() {
  return <DateInput label="Check-in" min="2026-01-01" max="2026-12-31" />;
}

Both bounds are inclusive. A completed date outside the range is still reported through onChange, with the field marked data-out-of-range and aria-invalid — silently swallowing what someone typed leaves them looking at a field that appears accepted and a form that will not submit, with nothing connecting the two. Set emitOutOfRange={false} if you would rather have null.

A min after the max is dropped entirely (and reported through onWarn): a field nothing can be entered into is worse than a missing bound.

Forms

With a name, the component emits a hidden input carrying the ISO value, so a native form posts it with no wiring:

import { DateInput } from "@rxova/react-date-input";

function Form() {
  return (
    <form action="/invoices" method="post">
      <DateInput label="Due" name="due" />
      <button type="submit">Save</button>
    </form>
  );
}

A hidden input is barred from constraint validation, so required is surfaced as aria-required on the group and enforcing it stays with your form layer. React Hook Form, Formik and the rest work through value/onChange as usual.

Styling

There is no stylesheet to import. Only layout-critical declarations are inlined; everything visual is a CSS custom property or a data-* hook.

| Property | Default | Applies to | | ----------------------------- | --------------------- | --------------------------- | | --rx-date-gap | 0.0625rem | Space between pieces | | --rx-date-segment-padding | 0 0.0625rem | Inside each segment | | --rx-date-segment-radius | 0.125rem | Segment corners | | --rx-date-literal-opacity | 0.7 | The separators | | --rx-date-focus-ring | 2px solid Highlight | Ring on the focused segment | | --rx-date-focus-ring-offset | 1px | Its offset |

The focused segment paints a ring by default. A <span role="spinbutton"> gets none from the browser, so overriding these is the supported way to restyle it — removing it outright leaves a keyboard user unable to tell which of the three segments they are on.

[data-rx-date-segment][data-focused] {
  background: Highlight;
  color: HighlightText;
}

[data-rx-date-segment][data-placeholder] {
  opacity: 0.55;
}

data-* attributes

These are public API, covered by semver.

| Attribute | On | Meaning | | --------------------------------- | ------------ | ----------------------------------------- | | data-rx-date-root | wrapper | Always present | | data-complete | wrapper | Every segment filled and the date is real | | data-invalid | wrapper | invalid prop, or out of range | | data-out-of-range | wrapper | Complete but outside min/max | | data-disabled / data-readonly | wrapper | Mirrors the props | | data-rx-date-segment | segment | day, month or year | | data-placeholder | segment | The segment is empty | | data-focused | segment | The segment has focus | | data-rx-date-value | hidden input | The ISO value a form posts |

Headless

useDateInput gives you the whole state machine with no markup — the digit buffer with auto-advance, day re-clamping, locale layout and focus management.

import { useDateInput } from "@rxova/react-date-input";

function CustomField() {
  const field = useDateInput({ locale: "en-GB" });

  return (
    <div onBlur={field.handleBlur}>
      {field.pieces.map((piece, index) =>
        piece.kind === "literal" ? (
          <span key={index}>{piece.text}</span>
        ) : (
          <span
            key={piece.type}
            ref={(node) => {
              field.segmentRefs.current[piece.type] = node;
            }}
            role="spinbutton"
            tabIndex={0}
            onFocus={(event) => {
              field.handleSegmentFocus(piece.type, event);
            }}
            onKeyDown={(event) => {
              if (/^\d$/.test(event.key)) field.typeDigit(piece.type, event.key);
              else if (event.key === "ArrowUp") field.step(piece.type, 1);
              else if (event.key === "ArrowDown") field.step(piece.type, -1);
            }}
          >
            {field.parts[piece.type] ?? "--"}
          </span>
        ),
      )}
    </div>
  );
}

The calendar helpers are exported too — toISO, fromISO, daysInMonth, isLeapYear, compareISO, withinRange — all pure, all Date-free.

Diagnostics

onWarn receives a { code, prop, received, message } whenever a prop is rejected or coerced:

import * as Sentry from "@sentry/react";
import { DateInput } from "@rxova/react-date-input";

function Field() {
  return (
    <DateInput
      label="Due"
      onWarn={(warning) => {
        Sentry.captureMessage(warning.message, { level: "warning", extra: { ...warning } });
      }}
    />
  );
}

Codes: value-unparseable, value-out-of-range, min-unparseable, max-unparseable, min-after-max, locale-invalid.

With no handler the same warnings go to console.warn. The entire path is stripped from production builds — it sits behind a process.env.NODE_ENV !== 'production' branch, so there is no runtime cost and no console noise in production. The E2E suite asserts this against a real production bundle.

Accessibility

  • A role="group" of three role="spinbutton" segments. A single control would leave a screen reader user unable to tell which part they are editing.
  • Each segment carries aria-valuemin / aria-valuemax / aria-valuenow, and the bounds narrow as the date fills in — the day range is 1–29 in a February, 1–28 once the year rules out a leap year.
  • The month announces as its name (aria-valuetext="March"), localised. "3" is the value; "March" is what the user is choosing.
  • An empty segment announces its placeholder and carries no aria-valuenow, rather than claiming a value of 0.
  • Separators are aria-hidden; reading "slash" between two named spinbuttons adds nothing.
  • Disabled segments stay in the accessibility tree but leave the tab order.
  • axe (WCAG 2.1 A/AA) runs over the component in the browser suite and over the whole demo page in Chromium, Firefox and WebKit — including a right-to-left locale.

UI-library recipes

The docs contain maintained examples for shadcn/ui, Radix Themes, Material UI, Chakra UI, Mantine and Ant Design. Copy a ready-labelled wrapper with:

npx shadcn@latest add https://rxova.dev/packages/react-inputs/r/date-field.json

Open the recipes.

Part of rxova

Part of the rxova headless React inputs suite — install the whole set from @rxova/react-inputs, or read the full generated API reference for this package.

Cross-cutting guidance lives on this component's About page: styling and form libraries. Coming from another library? The migration guide maps the props across.

License

MIT © rxova