Free · MIT · Violetta Studio

The date picker that doesn't drag a calendar library in with it.

Twenty files, twenty‑three React components for Next.js 16 and Tailwind v4. The date, range and time pickers are written by hand: no react‑day‑picker, no date‑fns, no dayjs. Radix does the popover positioning and the focus trapping; everything above that is plain React you can read in one sitting.

Next.js 16React 19TypeScript strict Tailwind v4Radix UIMIT

What you actually get

Twenty component files, twenty‑three exported components. Every one of them imports nothing but React, Radix and a four‑line cn().

Pickers

DatePicker, DateRangePicker (two months, or one), TimePicker, Combobox.

Selection

Select, DropdownMenu, Checkbox, RadioGroup and Radio.

Overlays

Dialog, ConfirmDialog, Tooltip, Toaster.

Forms

Field, Input, Textarea, Button, ButtonLink, Link.

Motion

Reveal and Stagger, with a first render that matches the server.

Nothing else

No CLI, no config to adopt, no npm package to depend on.

Why the pickers are the point

Most kits hand you a date field that pulls in a calendar library, a date library, and a plugin for the timezone edge cases. Here the 42‑cell month, the week offset and the month arithmetic are about forty lines of pure functions at the top of the file.

  • Dates cross the boundary as strings. "YYYY-MM-DD", never Date objects, never UTC. They sort chronologically as text, which is why the min/max checks are plain < and >.
  • Parsing stays local. The time is appended and the Z omitted, because a bare new Date("2026-03-29") is parsed as UTC and reports the day before anywhere west of Greenwich.
  • The full keyboard model. Arrows by day, PageUp/PageDown by month, Shift for years, Home/End, and a single tab stop for the whole grid.
  • Localised with no dependency. Pass a BCP‑47 tag and month names come from Intl.DateTimeFormat. Every other string is a prop.
import { DateRangePicker } from "@/components/ui/DateRangePicker";

<DateRangePicker
  startValue={from}         // "2026-08-12"
  endValue={to}
  onChange={(a, b) => setRange([a, b])}
  months={2}                // the default; 1 for a single grid
  min={todayISO}
  locale="es-CL"
/>

Themed from about fifteen variables

There is not one hard‑coded colour anywhere under src/components. Rebind the tokens and the whole set follows, including into a design system you already have.

Light and dark are both defined, in all three states a page can be in: the OS preference, and an explicit theme that beats it in either direction. The classic bug — a colour whose only definition sits inside a media query, so the page renders one theme's text on the other theme's background — cannot happen here.

Accessibility is in each component

Not a pass somebody promises to do later.

  • One focus ring defined globally, so no component invents its own colour.
  • Labels enforced by the types: several controls cannot be rendered unlabelled.
  • Native inputs kept in the DOM rather than replaced, so arrow keys between radios, Space to toggle a checkbox and one tab stop per group come from the platform instead of being re‑implemented badly.
  • prefers-reduced-motion honoured globally, with a safety net so a scroll reveal is never left invisible.

Take it

MIT. Use it in commercial work, no attribution required. Copy the files you want — there is no package to install and nothing to keep in sync.

Built by Violetta Studio. If you want these same primitives already assembled into a finished, production‑ready site, the paid templates live at violettadev.com.