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

dayfold

v0.1.0

Published

Headless, accessible, tiny date picker for any framework. Hijri, Persian and every Intl calendar built in — zero dependencies.

Readme

dayfold

npm minzipped size types license

An accessible, headless date picker for React, Vue and plain JavaScript — with Hijri, Persian and every other Intl calendar built in, in about 7.5 kB and zero dependencies.

dayfold handles the hard parts of a date picker — calendar math, keyboard navigation, ARIA, focus management, screen-reader announcements — and lets you render any markup with any styling. If you'd rather not build the markup, an optional ready-made <DateField> is included.

Live demos & docs · API reference · العربية · GitHub


Contents


Why dayfold

| | | | --- | --- | | Headless | You get state and prop getters for your own elements. Works with Tailwind, CSS Modules, styled-components, any design system — there is nothing to override. | | Accessible by default | Follows the W3C APG date-picker dialog and grid patterns with a real <button> in every cell, roving focus, polite live announcements, dialog focus trap and RTL-aware arrow keys. Tested with axe-core. | | Every calendar, no extra weight | Hijri (Umm al-Qura), Persian, Hebrew, Buddhist, Japanese… are computed from the browser's built-in Intl data, so they cost 0 extra bytes and need no add-on package. Show a second calendar under each day (e.g. Hijri under Gregorian). | | Every string is yours | Every aria-label, announcement and placeholder can be overridden per instance. Arabic ships built in. | | Tiny | ~7.5 kB min+gzip for the complete picker: single / range / multiple selection, popup, text input, month & year dropdowns. Zero dependencies, tree-shakeable. | | Framework-agnostic | One core with thin adapters: React 18+, Vue 3.5+, or plain DOM. | | Forms & SEO ready | Hidden inputs for native <form> posts, <time datetime> props, SSR-safe ids, and a fixed 6-week grid that never shifts layout. | | Modern dates | Values are plain ISO strings ("2026-09-29") — no time-zone bugs. Accepts Date, ISO strings and Temporal.PlainDate. |

Comparison

Measured in September 2026. Sizes are minified + gzipped JavaScript as a consumer bundles it (esbuild / Bundlephobia); styles for the other libraries are extra.

| | dayfold | cally | react-day-picker | flatpickr | react-datepicker | | --- | --- | --- | --- | --- | --- | | JS size (min+gzip) | 7.5 kB | 9.2 kB | 18.9 kB | 14.8 kB | 46.7 kB | | Dependencies | 0 | 1 | 2 | 0 | 3 | | Frameworks | React, Vue, vanilla | Web component | React | Vanilla | React | | Headless (your markup) | Yes | Partly (parts / CSS vars) | Partly (class names, components) | No | No | | Input + popup included | Yes | No | No (grid only) | Yes | Yes | | Hijri / Persian / Hebrew | Built in (Intl) | Not documented | Separate @daypicker/* add-ons | Not built in | Not built in | | Month & year dropdowns | Yes | — | Yes | Month only | Yes | | Declarative disabled rules | Yes | — | Yes | Yes | Yes | | Every a11y string overridable | Yes | Partly | Yes | Partly | Partly | | Time picking | No (dates only) | No | No | Yes | Yes |

Where others are stronger: react-day-picker has a larger ecosystem (it powers the shadcn/ui calendar); flatpickr and react-datepicker include time selection, which dayfold does not (yet). If you need a time picker today, pair dayfold with a time input or use one of those.


Installation

npm install dayfold
# or
pnpm add dayfold
# or
yarn add dayfold

React (18+) and Vue (3.5+) are optional peer dependencies — install whichever you use. Nothing else is installed.


Quick start

1. Ready-made field (React)

The fastest way: an accessible, styled field you can still customise.

import { useState } from 'react'
import { DateField } from 'dayfold/react/field'
import 'dayfold/field.css' // optional default look

export function BookingForm() {
  const [date, setDate] = useState<string | null>(null)

  return (
    <DateField
      label="Appointment date"
      value={date}
      onValueChange={setDate}
      disabled={{ before: new Date() }} // no past dates
      iconPosition="start"               // 'start' | 'end'
      name="appointment"                 // posts "2026-09-29" with the form
    />
  )
}

2. Headless React hook

Full control of the markup:

import { useDatePicker } from 'dayfold/react'

export function Calendar() {
  const dp = useDatePicker({ locale: 'en-US' })

  return (
    <div {...dp.getRootProps()}>
      <button {...dp.getPrevButtonProps()}>‹</button>
      <button {...dp.getNextButtonProps()}>›</button>

      {dp.months.map((month) => (
        <table key={month.start} {...dp.getGridProps(month)}>
          <caption {...dp.getMonthLabelProps(month)}>{month.label}</caption>
          <thead>
            <tr>
              {dp.weekdays.map((w) => (
                <th key={w.weekday} {...dp.getWeekdayProps(w)}>{w.narrow}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {month.weeks.map((week) => (
              <tr key={week[0].date} {...dp.getWeekProps()}>
                {week.map((day) => (
                  <td key={day.date} {...dp.getCellProps(day)}>
                    <button {...dp.getDayProps(day)}>{day.label}</button>
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      ))}

      <p>Selected: {dp.valueText ?? 'none'}</p>
    </div>
  )
}

3. Vue

<script setup>
import { useDatePicker } from 'dayfold/vue'

// Options can be an object, a ref, or a getter — changes are picked up reactively.
const dp = useDatePicker({ locale: 'ar-SA', calendar: 'islamic-umalqura' })
</script>

<template>
  <div v-bind="dp.getRootProps()">
    <button v-bind="dp.getPrevButtonProps()">‹</button>
    <button v-bind="dp.getNextButtonProps()">›</button>
    <table v-for="month in dp.months.value" :key="month.start" v-bind="dp.getGridProps(month)">
      <caption v-bind="dp.getMonthLabelProps(month)">{{ month.label }}</caption>
      <tr v-for="week in month.weeks" :key="week[0].date" v-bind="dp.getWeekProps()">
        <td v-for="day in week" :key="day.date" v-bind="dp.getCellProps(day)">
          <button v-bind="dp.getDayProps(day)">{{ day.label }}</button>
        </td>
      </tr>
    </table>
  </div>
</template>

4. Vanilla JavaScript

import { createDatePicker } from 'dayfold'
import { h } from 'dayfold/dom'

const dp = createDatePicker({ locale: 'fa-IR', calendar: 'persian' })
const root = document.querySelector('#calendar')

function render() {
  const [month] = dp.getMonths()
  root.replaceChildren(
    h('table', dp.getGridProps(month),
      h('caption', dp.getMonthLabelProps(month), month.label),
      ...month.weeks.map((week) =>
        h('tr', dp.getWeekProps(),
          ...week.map((day) =>
            h('td', dp.getCellProps(day), h('button', dp.getDayProps(day), day.label)))))),
  )
}

dp.subscribe(render)
render()

Focus is restored to the right day after every render, so a full re-render like this stays keyboard-friendly. Update options later with dp.update({ ...options }).


DateField props

import { DateField } from 'dayfold/react/field' — accepts every picker option plus:

| Prop | Type | Default | Description | | --- | --- | --- | --- | | label | ReactNode | — (required) | Visible label, associated with the input. | | hideLabel | boolean | false | Hide the label visually but keep it for screen readers. | | description | ReactNode | — | Hint below the field, linked with aria-describedby. | | error | ReactNode | — | Error text; sets aria-invalid and is linked with aria-describedby. | | icon | ReactNode \| false | calendar icon | Content of the trigger button — any SVG, icon-library component, emoji or text. false removes the trigger. | | iconPosition | 'start' \| 'end' | 'end' | Where the trigger sits. Changes DOM order (so Tab order matches) and mirrors automatically in RTL. | | dropdowns | boolean | true | Month and year <select>s in the popup header. | | prevIcon / nextIcon | ReactNode | chevrons | Icons for the month navigation buttons. | | renderDay | (day: CalendarDay) => ReactNode | day number | Custom day content (the accessible name still comes from labels.day). | | inputProps | InputHTMLAttributes | — | Extra input attributes: required, autoFocus, className, … | | className | string | — | Added to the root element. | | classNames | Partial<Record<Part, string>> | — | Class per part: root, label, description, error, control, input, trigger, popover, header, selects, select, nav, navButton, months, grid, weekday, cell, day. |

Every part also has a stable dayfold-* class (e.g. .dayfold-trigger), and the root carries data-icon-position, data-open and data-invalid.

<DateField label="Start date" icon={<CalendarDays size={18} />} iconPosition="start" />
<DateField label="Check-in" icon={<span aria-hidden="true">📅</span>} />
<DateField label="Date of birth" icon={false} placeholder="dd/mm/yyyy" />
<DateField label="Date" classNames={{ input: 'input input-bordered', day: 'btn btn-ghost' }} />

Theming field.css — every value is a custom property; light / dark is automatic:

.my-form .dayfold-field {
  --dayfold-accent: #0f766e;
  --dayfold-on-accent: #fff;
  --dayfold-accent-soft: #ccfbf1;
  --dayfold-bg: #fff;
  --dayfold-fg: #111;
  --dayfold-muted: #555;
  --dayfold-border: #ccc;
  --dayfold-danger: #b42318;
  --dayfold-focus: #0f766e;
  --dayfold-radius: 12px;
  --dayfold-day-size: 44px;
}

Picker options

Used by useDatePicker() (React / Vue), createDatePicker() (vanilla) and <DateField>.

Selection

| Option | Type | Default | Description | | --- | --- | --- | --- | | mode | 'single' \| 'range' \| 'multiple' | 'single' | Selection mode. | | value | see below | — | Controlled value. Pass null (or []) for "nothing selected". | | defaultValue | see below | — | Initial value for uncontrolled use. | | onValueChange | (value, { date, source }) => void | — | Called on every change. source: 'click' \| 'keyboard' \| 'input' \| 'clear' \| 'api'. | | maxSelections | number | — | Multiple mode: maximum number of dates. | | allowDisabledInRange | boolean | false | Range mode: allow a range to span disabled dates. |

Values are always returned as ISO strings: string | null (single), string[] (multiple), { start, end } | null (range). Inputs also accept Date and Temporal.PlainDate.

Restricting dates

| Option | Type | Default | Description | | --- | --- | --- | --- | | min / max | date | — | Earliest / latest selectable date. Also stops navigation and trims the dropdowns. | | disabled | DateMatcher \| DateMatcher[] | — | Dates that can't be selected — see disabling dates. | | isDateDisabled | (iso: string) => boolean | — | Function form (same as a function inside disabled). |

Calendar & locale

| Option | Type | Default | Description | | --- | --- | --- | --- | | locale | BCP 47 tag | runtime locale | Formatting, week start, weekend and direction, e.g. 'en-US', 'ar-SA'. | | calendar | calendar id | 'gregory' | 'islamic-umalqura', 'islamic-civil', 'persian', 'hebrew', 'buddhist', 'japanese'… | | secondaryCalendar | calendar id | — | Second calendar shown under each day. | | numberingSystem | 'latn' \| 'arab' \| … | from locale | Digits to display. | | dir | 'ltr' \| 'rtl' | from locale | Writing direction (also flips arrow keys). | | weekStartsOn | 0–6 | from locale | First day of the week (0 = Sunday). |

Display & navigation

| Option | Type | Default | Description | | --- | --- | --- | --- | | numberOfMonths | number | 1 | Months shown side by side. | | fixedWeeks | boolean | true | Always 6 rows, so the grid never changes height (no layout shift). | | years | { from?, to? } | min/max, else −100 / +50 | Year range for the year dropdown, in the calendar's own numbering. | | defaultFocusedDate | date | selection, then today | Initially focused / visible date. | | today | date | local today | Override "today" (tests, SSR). |

Popup

| Option | Type | Default | Description | | --- | --- | --- | --- | | open / defaultOpen | boolean | false | Controlled / initial popup state. | | onOpenChange | (open) => void | — | Called when the popup opens or closes. | | closeOnSelect | boolean | true | Close after picking a date (or a full range). | | closeOnOutsideClick | boolean | true | Close on pointer-down outside the popup. | | modal | boolean | true | Trap Tab inside the popup. |

Text input & forms

| Option | Type | Default | Description | | --- | --- | --- | --- | | inputFormat | Intl.DateTimeFormatOptions | numeric y/m/d | How the value is shown in the input. | | parse | (text, { locale, calendar, today }) => iso \| null | locale-aware | Custom parser. Use withShortcuts() for "tomorrow", "+3d"… | | placeholder | string | e.g. mm/dd/yyyy | Input placeholder. | | name | string | — | Field name for getHiddenInputProps() / DateField. |

Accessibility

| Option | Type | Default | Description | | --- | --- | --- | --- | | labels | Partial<Labels> | English | Override any accessible string — see labels. | | announce | 'auto' \| 'manual' \| false | 'auto' | 'auto' uses a shared live region; 'manual' exposes state.announcement for your own. | | id | string | auto | Stable id prefix (pass one for SSR if not using the React / Vue adapters). | | focusDay | (iso) => void | built in | Custom focus routine if you don't render data-date attributes. |


Returned API

| Member | Description | | --- | --- | | months / getMonths() | Visible months, each with label, weeks of CalendarDay and more. | | weekdays / getWeekdays() | Weekday names in display order (narrow, short, long). | | value / getValue() | Effective value (controlled or not). | | valueText / getValueText() | Localised text, e.g. "Sep 29 – Oct 3, 2026". | | state / getState() | focusedDate, visibleDate, open, inputText, inputInvalid, announcement. | | monthOptions / yearOptions | Entries { value, label, disabled } for dropdowns. | | select(iso), clear() | Change the selection. | | focus(iso) | Move the focused / visible date. | | goToMonth(n), nextMonth(), prevMonth() | Navigate months. | | goToYear(year), goToMonthIndex(i) | Jump to a year (calendar numbering) or a month of the visible year. | | setOpen(bool), toggle(), isOpen() | Popup control. | | isDateDisabled(iso), hasInvalidValue() | Checks against the current options. | | getDirection() | Resolved 'ltr' / 'rtl'. | | subscribe(fn), update(options), destroy() | Store helpers for vanilla use. |

In React and Vue, months, weekdays, value, valueText, monthOptions and yearOptions are provided directly (in Vue as computed refs).

Prop getters

Each returns a plain object of attributes and handlers — spread it in React, v-bind it in Vue, or apply it with spread() / h() from dayfold/dom.

| Getter | Put it on | Provides | | --- | --- | --- | | getRootProps() | wrapper | dir, data-dayfold | | getLabelProps() | <label> | id, htmlFor → input | | getInputProps() | <input> | value, placeholder, aria-invalid, parse on blur / Enter, Alt+↓ opens | | getTriggerProps() | <button> | aria-haspopup, aria-expanded, aria-controls, dynamic aria-label | | getDialogProps() | popup | role="dialog", aria-modal, aria-label, Esc, focus trap | | getPrevButtonProps() / getNextButtonProps() | <button> | aria-label, aria-disabled at min / max | | getMonthSelectProps() / getYearSelectProps() | <select> | aria-label, value, onChange | | getClearButtonProps() | <button> | aria-label, clears the value | | getMonthLabelProps(month) | heading / caption | id that labels the grid | | getGridProps(month) | <table> | role="grid", aria-labelledby, aria-multiselectable | | getWeekdayProps(weekday) | <th> | role="columnheader", full weekday name | | getWeekProps() | <tr> | role="row" | | getCellProps(day) | <td> | role="gridcell", aria-selected, data-* state | | getDayProps(day) | <button> | roving tabIndex, aria-label, aria-current, aria-disabled, keyboard | | getLiveRegionProps() | element | role="status" (with announce: 'manual') | | getHiddenInputProps({ name, part }) | <input> | type="hidden" ISO value for native forms | | getTimeProps(date?) | <time> | dateTime |

To add your own handler, call dayfold's too: onClick={(e) => { track(); dp.getDayProps(day).onClick(e) }}.

Calendar data

interface CalendarDay {
  date: string            // '2026-09-29'
  day: number             // day of month in the primary calendar
  label: string           // localised digits, e.g. '٢٩'
  fullLabel: string       // 'Tuesday, September 29, 2026'
  secondaryLabel?: string // day in secondaryCalendar
  secondaryFullLabel?: string
  weekday: number         // 0 = Sunday
  isToday, isSelected, isDisabled, isOutside, isFocused, isWeekend: boolean
  isRangeStart, isRangeEnd, isInRange, isInPreview: boolean
}

Guides

Disabling dates

import { today } from 'dayfold'

useDatePicker({
  disabled: [
    { before: today() },                           // all past dates
    { after: '2027-06-30' },                       // everything after a date
    { from: '2026-12-24', to: '2026-12-26' },      // an inclusive range (either end optional)
    { before: '2026-01-01', after: '2026-12-31' }, // everything outside a window
    '2026-11-11',                                  // one date (string, Date or Temporal)
    { dayOfWeek: [5, 6] },                         // weekdays (0 = Sunday) — here Fri & Sat
    (iso) => holidays.has(iso),                    // any custom rule
  ],
})

Disabled dates stay focusable and are announced as "unavailable", so keyboard and screen-reader users can still explore; ranges can't span them. Use min / max when navigation should stop too.

Linked start & end fields

Options are re-read on every render, so one field can restrict another at runtime:

const [start, setStart] = useState<string | null>(null)
const [end, setEnd] = useState<string | null>(null)

<DateField label="Start date" value={start} onValueChange={setStart}
  disabled={end ? { after: end } : undefined} />
<DateField label="End date" value={end} onValueChange={setEnd}
  disabled={start ? { before: start } : undefined} />
  • A picker opening without a value focuses the nearest selectable day — the end picker opens on the start date.
  • If a value becomes disabled because the other field changed, its input gets aria-invalid="true" and hasInvalidValue() returns true.
  • Vue: useDatePicker(() => ({ disabled: start.value ? { before: start.value } : undefined })). Vanilla: endPicker.update({ ...options, disabled: { before: start } }).

Range and multiple selection

const range = useDatePicker({ mode: 'range', numberOfMonths: 2 })
// range.value → { start: '2026-10-01', end: '2026-10-05' }

const multi = useDatePicker({ mode: 'multiple', maxSelections: 5 })
// multi.value → ['2026-10-01', '2026-10-03']

Days expose isRangeStart, isRangeEnd, isInRange and isInPreview (the tentative range while choosing the end) plus matching data-* attributes.

Month & year dropdowns

<select {...dp.getMonthSelectProps()}>
  {dp.monthOptions.map((m) => <option key={m.value} value={m.value} disabled={m.disabled}>{m.label}</option>)}
</select>
<select {...dp.getYearSelectProps()}>
  {dp.yearOptions.map((y) => <option key={y.value} value={y.value} disabled={y.disabled}>{y.label}</option>)}
</select>

Options are localised and calendar-aware (Hijri years like 1448, 13 months in a Hebrew leap year). Keep the dropdowns mounted across month changes so they keep focus.

Text input & typed shortcuts

import { withShortcuts } from 'dayfold/shortcuts'

const dp = useDatePicker({ locale: 'en-GB', parse: withShortcuts() })

<label {...dp.getLabelProps()}>Departure</label>
<input {...dp.getInputProps()} />
<button {...dp.getTriggerProps()}>📅</button>
{dp.state.open && <div {...dp.getDialogProps()}>{/* calendar */}</div>}

The default parser follows the locale's numeric order (29/09/2026 in en-GB, 09/29/2026 in en-US) and accepts ISO dates and Arabic-Indic digits. withShortcuts() adds today, tomorrow, +3d, -2w, in 1 month, next fri, and Arabic اليوم, غدا, أمس, بعد 3 أيام.

dayfold manages the popup's open state, focus on open, Escape, the focus trap, outside clicks and returning focus to the trigger; positioning is yours (the native popover attribute or CSS anchor positioning work well).

Hijri and other calendars

// Hijri (Umm al-Qura) with Gregorian beneath each day — Arabic digits and RTL come from the locale
useDatePicker({ locale: 'ar-SA', calendar: 'islamic-umalqura', secondaryCalendar: 'gregory' })

// Gregorian with Hijri beneath, Latin digits
useDatePicker({ locale: 'en-SA', secondaryCalendar: 'islamic-umalqura', numberingSystem: 'latn' })

// Persian (Jalali)
useDatePicker({ locale: 'fa-IR', calendar: 'persian' })

Arabic accessible labels:

import { ar } from 'dayfold/locales/ar'
useDatePicker({ locale: 'ar-SA', labels: ar })

Styling

State is exposed as data attributes on both the cell and the day button:

[data-selected] { background: black; color: white; }
[data-today] { font-weight: 700; }
[data-outside] { color: gray; }
[data-disabled] { text-decoration: line-through; }
[data-in-range], [data-preview] { background: #eee; }
[data-range-start] { border-radius: 99px 0 0 99px; }
[data-range-end] { border-radius: 0 99px 99px 0; }
[aria-disabled='true'] { cursor: not-allowed; }

Tailwind: data-[selected]:bg-black data-[outside]:text-gray-400 aria-disabled:cursor-not-allowed.

Forms, SSR & SEO

  • Native forms: <input {...dp.getHiddenInputProps({ name: 'date' })} /> posts the ISO value. Range mode sends start/end, or one input per end with part: 'start' | 'end'.
  • Server rendering: no browser globals are touched at import or render. React and Vue use useId() for hydration-safe ids. Pass locale (and today if needed) so server and client agree. Node and browsers can ship different ICU data, so add suppressHydrationWarning to formatted month names or render the calendar client-only.
  • Machine-readable dates: <time {...dp.getTimeProps()}> renders datetime="2026-09-29".
  • No layout shift: fixedWeeks (default) keeps every month at six rows.

Accessibility

  • W3C APG date-picker dialog and grid patterns, with a real <button> in every cell.
  • One Tab stop for the grid (roving tabindex); disabled dates remain focusable and are announced as unavailable.
  • Month and selection changes are announced in a polite live region.
  • Popup: role="dialog", aria-modal, focus moves in on open, Tab is trapped, Escape closes and returns focus to the trigger.
  • State is never colour-only — it is in the accessible name and in data attributes.
  • Arrow keys follow reading direction in RTL.
  • Verified with axe-core (WCAG 2.2 AA rules) in the test suite.

Labels

Every accessible string can be overridden per instance:

| Label | Default | Used for | | --- | --- | --- | | dialog | "Choose date" | Popup name | | prevMonth / nextMonth | "Previous month" / "Next month" | Navigation buttons | | monthSelect / yearSelect | "Month" / "Year" | Dropdowns | | clear | "Clear" | Clear button | | today, selected, unavailable, rangeStart, rangeEnd | "Today", "selected", … | Words inside day labels | | trigger(valueText) | "Choose date" / "Change date, Sep 29, 2026" | Trigger button name | | day(day, labels) | "Today, Tuesday, September 29, 2026, selected" | Full day label | | monthChanged(label) | the month label | Announcement after navigation | | selectionChanged(valueText) | "Selected Sep 29, 2026" | Announcement after a selection |

useDatePicker({
  labels: {
    dialog: 'Choose your check-in date',
    day: (d, l) => [d.fullLabel, d.isDisabled && 'fully booked'].filter(Boolean).join(', '),
  },
})

You still own the rest of the page: give the input a visible label, keep text contrast at 4.5:1 and day buttons at least 24×24 px (44 px recommended).

Keyboard

| Key | Action | | --- | --- | | ← / → | Previous / next day (swapped in RTL) | | ↑ / ↓ | Same weekday, previous / next week | | Home / End | First / last day of the week | | Page Up / Page Down | Previous / next month | | Shift + Page Up / Page Down | Previous / next year | | Enter / Space | Select the focused day | | Esc | Close the popup and return focus | | Tab / Shift + Tab | Move between popup controls (trapped when modal) | | Alt + ↓ (in input) | Open the popup | | Enter (in input) | Parse and commit the typed date |

Browser support

All evergreen browsers: Chrome / Edge 92+, Firefox 90+, Safari 15.4+ (iOS 15.4+). The optional field.css uses light-dark() for automatic dark mode where supported and falls back to the light theme elsewhere. Node 18+ for server rendering and tests.

Entry points & sizes

| Import | Contents | Size (min+gzip) | | --- | --- | --- | | dayfold | createDatePicker() + date utilities | 7.5 kB | | dayfold/react | useDatePicker() (includes core) | 7.7 kB | | dayfold/vue | useDatePicker() (includes core) | 7.9 kB | | dayfold/react/field | <DateField> (includes core) | 8.8 kB | | dayfold/field.css | Optional styles for <DateField> | 1.6 kB | | dayfold/dom | spread(), h() for vanilla JS | 0.3 kB | | dayfold/shortcuts | Typed shortcuts parser | 1.8 kB | | dayfold/locales/ar | Arabic accessible labels | < 0.5 kB |

Size budgets are enforced in CI — a change that grows a bundle past its budget fails the build.

FAQ

Does it include a time picker? Not yet — dayfold picks dates. Pair it with <input type="time"> or your own time control.

What value format do I get? ISO date strings ("2026-09-29"), never Date objects, so there are no time-zone shifts. Convert with toDate(iso) or Temporal.PlainDate.from(iso).

Does the Hijri calendar match the official Umm al-Qura calendar? It uses the browser's ICU islamic-umalqura data, which is based on the official Umm al-Qura tables.

Can I use it with Next.js / Nuxt? Yes — it is SSR-safe. See Forms, SSR & SEO.

Angular, Svelte, Solid? Use the framework-free core (createDatePicker + subscribe) — the prop getters are plain objects.

License

MIT