@calmjs/datepicker
v1.0.2
Published
A headless, production-ready React datepicker component that works with any form library. Fully customizable with Tailwind CSS, CSS Modules, or inline styles.
Maintainers
Readme
@calmjs/datepicker
A headless, production-ready React datepicker component that works with any form library.
✨ Works seamlessly with React Hook Form, Formik, Yup, Zod, or standalone
🎨 Fully customizable with Tailwind CSS, CSS Modules, or inline styles
📦 TypeScript support with full type definitions
🚀 Zero dependencies (except React peer deps)
Installation
npm install @calmjs/datepickerQuick Start
Standalone Usage
import { useState } from "react";
import { DatePicker } from "@calmjs/datepicker";
function App() {
const [date, setDate] = useState<Date | null>(null);
return (
<DatePicker
value={date}
onChange={setDate}
label="Select Date"
placeholder="Pick a date..."
/>
);
}With React Hook Form + Zod
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { DatePicker } from "@calmjs/datepicker";
const schema = z.object({
birthDate: z
.date({
required_error: "Date of birth is required",
})
.refine((d) => d <= new Date(), {
message: "Cannot be in the future",
}),
});
function MyForm() {
const { control, handleSubmit } = useForm({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="birthDate"
control={control}
render={({ field, fieldState }) => (
<DatePicker
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
error={fieldState.error?.message}
touched={fieldState.isTouched}
label="Date of Birth"
/>
)}
/>
<button type="submit">Submit</button>
</form>
);
}With Formik + Yup
import { Formik, Form } from "formik";
import * as Yup from "yup";
import { DatePicker } from "@calmjs/datepicker";
const schema = Yup.object({
startDate: Yup.date()
.required("Start date is required")
.min(new Date(2020, 0, 1), "Must be after Jan 2020"),
});
function MyForm() {
return (
<Formik
initialValues={{ startDate: null }}
validationSchema={schema}
onSubmit={(values) => console.log(values)}
>
{({ setFieldValue, values, errors, touched }) => (
<Form>
<DatePicker
value={values.startDate}
onChange={(date) => setFieldValue("startDate", date)}
error={errors.startDate}
touched={touched.startDate}
label="Project Start Date"
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
}API Reference
DatePicker Props
| Prop | Type | Description |
| ------------- | ------------------------------ | ------------------------------------------- |
| value | Date \| null | Controlled value from form state |
| onChange | (date: Date \| null) => void | Updates form state |
| onBlur | () => void | Marks field as touched (for form libraries) |
| error | string | Validation error message |
| touched | boolean | Show error only after interaction |
| name | string | Field name for form registration |
| label | string | Accessible label text |
| placeholder | string | Placeholder text when no date selected |
useDatePicker Hook
For advanced usage, you can use the headless useDatePicker hook directly:
import { useDatePicker } from "@calmjs/datepicker";
function CustomDatePicker() {
const dp = useDatePicker({
value: selectedDate,
onChange: setSelectedDate,
weekStartsOn: 1, // Monday
});
return (
<div>
<button onClick={dp.toggle}>
{dp.selectedDate?.toLocaleDateString() || "Select date"}
</button>
{dp.isOpen && (
<div>
<button onClick={dp.goToPrevMonth}>←</button>
<span>
{dp.monthName} {dp.year}
</span>
<button onClick={dp.goToNextMonth}>→</button>
<div>
{dp.days.map((day, i) => (
<button
key={i}
onClick={() => dp.selectDate(day.date)}
disabled={!day.isCurrentMonth}
>
{day.day}
</button>
))}
</div>
</div>
)}
</div>
);
}Styling Customization
With Tailwind CSS
The DatePicker component supports custom styling through className props. You can override any part of the component with Tailwind classes:
<DatePicker
value={date}
onChange={setDate}
label="Select Date"
// Container styling
className="w-full max-w-md"
// Input button styling
inputClassName="bg-slate-800 border-blue-500 hover:border-blue-400 focus:ring-2 focus:ring-blue-500"
// Label styling
labelClassName="text-blue-300 font-semibold"
// Calendar dropdown styling
calendarClassName="bg-slate-900 shadow-2xl border-slate-700"
// Day cells styling
cellClassName="hover:bg-blue-600 transition-colors"
// Selected day styling
selectedClassName="!bg-blue-500 !text-white"
// Today indicator styling
todayClassName="ring-2 ring-blue-400"
/>Available className Props
| Prop | Applies To |
| ----------------------- | -------------------------------- |
| className | Container wrapper |
| labelClassName | Label text |
| inputClassName | Input button |
| errorClassName | Error message |
| calendarClassName | Calendar dropdown |
| headerClassName | Calendar header section |
| navButtonClassName | Previous/Next navigation buttons |
| selectorClassName | Month/Year selector buttons |
| weekdayClassName | Weekday header (S M T W T F S) |
| cellClassName | Day/Month/Year cells |
| selectedClassName | Selected cell state |
| todayClassName | Today indicator state |
| footerClassName | Footer section |
| footerButtonClassName | Clear/Today footer buttons |
With CSS Modules
import styles from "./DatePicker.module.css";
<DatePicker
className={styles.container}
inputClassName={styles.input}
calendarClassName={styles.calendar}
cellClassName={styles.cell}
selectedClassName={styles.selected}
/>;Inline Styles Still Work
All components retain their default inline styles, ensuring they work out-of-the-box. Your custom classes are added alongside these defaults, giving you full control over which styles to override.
// This works perfectly - default beautiful styles included
<DatePicker value={date} onChange={setDate} />
// This also works - your Tailwind classes override the defaults
<DatePicker
value={date}
onChange={setDate}
inputClassName="bg-gradient-to-r from-purple-500 to-pink-500"
/>Local Testing Before Publishing
Before publishing to npm, test the package locally in your project:
1. Build the package
cd c:\Users\amar.m\Documents\CalmJS
npm install
npm run build2. Link the package globally
npm link3. Use it in your test project
cd path\to\your\test\project
npm link @calmjs/datepicker4. Import and test
import { DatePicker } from "@calmjs/datepicker";
// Use the component as shown in examples above5. Unlink when done
# In your test project
npm unlink @calmjs/datepicker
# In the package directory
npm unlinkPublishing to npm
Once you've tested locally and everything works:
# 1. Login to npm (first time only)
npm login
# 2. Build the package
npm run build
# 3. Publish
npm publish --access public
# For scoped packages (@your-org/package-name)
# Make sure to use --access public for the first publishVersion Management
# Patch version (1.0.0 → 1.0.1) - bug fixes
npm version patch
# Minor version (1.0.1 → 1.1.0) - new features
npm version minor
# Major version (1.1.0 → 2.0.0) - breaking changes
npm version major
# Then publish
npm publishWhy It Works with Every Form Library
The component accepts value + onChange — the universal controlled component pattern, identical to native inputs. Any form library that supports controlled inputs works automatically with zero adapters.
Features
- ✅ Controlled & uncontrolled modes
- ✅ Full Tailwind CSS & custom styling support
- ✅ Works with all major form libraries (React Hook Form, Formik, etc.)
- ✅ Year/Month/Day grid navigation
- ✅ TypeScript with full autocomplete
- ✅ Lightweight (~10KB)
- ✅ Accessible (keyboard navigation, ARIA labels)
License
MIT
