@ragyjs/date-picker
A lightweight, modern, and responsive date picker for web apps - supports single and range selection, built-in presets (Today, Last 7 Days, etc.), flexible theming with CSS variables, and full keyboard and screen reader accessibility. Framework‑agnostic. Zero peer dependencies. Ships with one small runtime helper (
@ragyjs/dom-renderer).
Try the live demo → View Demo

If you find this project useful, consider giving it a ⭐ on GitHub. It helps others discover it and keeps the project going!
Documentation
Table of Contents
- What is this?
- Features
- Installation
- Quick Start
- Core Concepts
- Styling & Theming
- Options
- Methods (Public API)
- Localization
- Accessibility
- Recipes
- Framework Usage
- Performance
- FAQ
- License
- Support
What is this?
This is a modern, responsive date picker that works in any web project. It supports both single date and date range selection, and comes with helpful presets like “Today”, “Last 7 Days”, and others ready to use.
The styling is fully customizable using simple CSS variables. Keyboard navigation and screen reader support are built in. It uses a minimal rendering helper under the hood (@ragyjs/dom-renderer), but has no other dependencies - no frameworks, no large libraries.
You can drop it into any page, connect it to your logic, and it just works. If you're building a dashboard, analytics filter, or anything that needs a clean, flexible way to pick dates, this tool was made for that.
Features
- Supports both single date and date range selection
- Includes built-in presets like “Today”, “Last 7 Days”, “This Month”, etc.
- Works with any framework - or no framework at all
- Fully responsive layout, adapts to small screens
- Easy to customize with CSS variables
- Accessible by keyboard and screen readers
- Comes with built-in themes out of the box
- Easily customizable — override styles with CSS variables or create your own themes
- Fast to load, no extra dependencies
- Supports localization and custom language overrides
- Clean API for integration, with useful events and methods
Installation
Install the package using your preferred package manager:
npm install @ragyjs/date-picker
# or
pnpm add @ragyjs/date-picker
# or
yarn add @ragyjs/date-pickerImporting
Import the JavaScript module and the CSS file into your project:
import { DatePicker } from '@ragyjs/date-picker';
import '@ragyjs/date-picker/style.css';The package is ESM-only. You can use it in any modern frontend stack (Vite, Webpack, etc.) or plain <script type="module"> in the browser.
SSR note: Import is safe in SSR, but only create the picker in the browser (e.g., inside useEffect/onMounted).
Quick Start
Here’s how to get a date picker up and running in a few lines of code.
You can use it in single or range mode depending on your needs.
Single date picker
import { DatePicker } from '@ragyjs/date-picker';
import '@ragyjs/date-picker/style.css';
const container = document.querySelector('#single-picker');
new DatePicker(container, {
mode: 'single',
placeholder: 'Select a date',
onApply: (date) => {
// Returns a single date object: { day, month, year }
console.log('Selected date:', date);
}
});Range picker with presets
import { DatePicker } from '@ragyjs/date-picker';
import '@ragyjs/date-picker/style.css';
const container = document.querySelector('#range-picker');
new DatePicker(container, {
mode: 'range',
placeholder: 'Pick a date range',
maxRange: 30,
onApply: (range) => {
// Returns an array: [startDate, endDate], each as { day, month, year }
console.log('Selected range:', range);
}
});Core Concepts
Before you dive deeper, here are a few things to understand about how this date picker works.
Date object shape
Instead of using native JavaScript Date objects, this picker uses plain date objects in the following format:
{ day: 7, month: 10, year: 2025 }monthis 1-based (January = 1, December = 12)- This format is easy to serialize, compare, and use across time zones
You’ll receive this format from onApply, and you’ll also use it when passing in default value.
Modes: single vs. range
You can choose between two modes:
'single'- the picker lets users select just one date'range'- the picker shows a two-part range selector with optional presets
The value returned by onApply depends on the mode:
- Single mode returns a date object:
{ day, month, year } - Range mode returns an array:
[startDate, endDate]
Inclusive ranges
All date ranges are inclusive.
If a user selects “October 1” to “October 7”, the range includes both dates.
Start of week
By default, weeks start on Sunday (0), but you can change that using the startOfWeek option.
You can pass either a number (0–6) or a string (like 'mon', 'tue', etc.).
new DatePicker(container, {
startOfWeek: 'mon' // makes Monday the first day of the week
// Valid values: 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
});Styling & Theming
The date picker is styled using CSS variables, so it’s easy to customize and theme without touching the JavaScript.
CSS import
The picker requires its CSS file to be included. Without it, the component will render without styles.
Make sure you import the CSS file in your app:
import '@ragyjs/date-picker/style.css';Built-in themes
The picker currently includes the following built-in themes:
cerulean– clean and light with a blue highlight (default)raspberry– bold and playful with raspberry tonesviridian– calm green and neutral gray tonescaribbean– fresh and vibrant, inspired by tropical watersdark– dark mode theme with soft contrast
You can apply it explicitly:
new DatePicker(container, {
theme: 'cerulean'
});Custom themes with CSS variables
You can fully customize the look of the picker by defining your own CSS theme using variables.
Each theme is applied by targeting .rjs-datePicker.rjs-yourThemeName.
Here’s an example:
.rjs-datePicker.rjs-customTheme {
--color-primary: #0055cc; /* Main color */
--color-background: #ffffff; /* Main background of the picker */
--color-background-alt: #f0f0f0; /* Background of hover/focus states */
--color-background-skeleton: #00000005; /* Shimmer background while loading */
--color-text: #1a1a1a; /* Text color for labels and days */
--color-border: #d0d0d0; /* Border color */
--color-placeholder: #999999; /* Placeholder and dimmed text */
--color-icons: #888888; /* Icons stroke color */
--color-error: #d00000; /* Error message color */
--shadow-day: 0px 0px 4px #00000023; /* Hover shadow on day cells */
--shadow-button: 0px 0px 2px #00000023; /* Shadow on buttons */
--shadow-dropDown: 0 3px 8px rgba(0,0,0,.24); /* Shadow for month/year dropdown */
}Then apply the theme:
new DatePicker(container, {
theme: 'customTheme'
});Instance-level overrides with the style option
You don’t have to define a full theme class to customize colors.
You can pass CSS variables directly using the style option when creating the picker.
new DatePicker(container, {
theme: 'cerulean',
style: {
'--color-primary': '#E63946',
'--color-background': '#fffef8',
'--shadow-day': '0 0 6px rgba(0,0,0,.25)'
}
});You can also change variables at runtime:
picker.picker.style.setProperty('--color-primary', '#7B61FF');This approach is perfect for dynamic or per-user theming, without writing custom CSS classes.
CSS variables available
Here’s the full list of CSS variables you can override:
--color-primary--color-background--color-background-alt--color-background-skeleton--color-text--color-border--color-placeholder--color-icons--color-error--shadow-day--shadow-button--shadow-dropDown
Options
You can pass options as the second argument when creating a new picker:
new DatePicker(container, { ...options });Here’s a complete list of available options:
mode
- Type:
'single' | 'range' - Default:
'single' - What it does:
Defines whether the picker selects a single date or a date range.
value
- Type:
- In
'single'mode:{ day, month, year } - In
'range'mode:[startDate, endDate](same format)
- In
- What it does:
Sets the initial selected value. Useful for pre-filling the picker with a known date or range.
placeholder
- Type:
string - Default:
'' - What it does:
Text shown when no date is selected yet.
label
- Type:
string - What it does:
Adds a visible label above the picker input.
format
- Type:
string - Default:
'dd-M-YY' - What it does:
Controls how dates are displayed inside the input.
You can use tokens likedd,mm,YYYY,M,D, etc.
See Localization for full token list.
theme
- Type:
string - Default:
'cerulean' - What it does:
Applies a built-in or custom theme by name.
See Styling & Theming for available themes.
style
- Type:
object - What it does:
Adds inline styles to the picker root.
You can use this to set layout styles (like width), or override theme variables per instance.
locale
- Type:
string - Default:
'en' - What it does:
Selects one of the built-in locales (e.g.,'fr','ar','de').
See Localization for supported codes.
trans
- Type:
object - What it does:
Allows you to override or define custom translation labels.
You can override only some keys, or provide a full set.
startOfWeek
- Type:
number | string - Default:
0(Sunday) - What it does:
Defines which day starts the week (0= Sunday,1= Monday, etc.), or pass'mon','tue', etc.
minDate
- Type:
{ day, month, year } - What it does:
Limits how early users can select dates.
Navigation (prev/next month arrows and the month/year dropdown) will not move the view beyond this boundary. ProgrammaticgoTo()is not clamped by design.
maxDate
- Type:
{ day, month, year } - What it does:
Limits how late users can select dates.
Navigation (prev/next month arrows and the month/year dropdown) will not move the view beyond this boundary. ProgrammaticgoTo()is not clamped by design.
maxRange
- Type:
number(days) - What it does:
In range mode, limits how long the selected range can be.
The range is inclusive (e.g.,7means start + 6 days). The preset list is filtered automatically so only presets that fit within yourmaxRange(andminDate/maxDate) are shown.
onApply
- Type:
(value) => void When it fires:
When the user clicks the Apply button.Value shape:
- In single mode:
{ day, month, year } - In range mode:
[startDate, endDate]
- In single mode:
onPick
- Type:
(value) => void When it fires:
When the user selects a day or changes the range (before clicking Apply).Value shape:
Same asonApply.
onClear
- Type:
() => void - When it fires:
When the user clicks the Clear button.
Methods (Public API)
When you create a new picker, it returns an instance with several helpful methods:
const picker = new DatePicker(container, options);You can use the following methods to interact with it programmatically.
picker.value
- Type:
- In
'single'mode:{ day, month, year } - In
'range'mode:[startDate, endDate]
- In
- What it does:
Returns the current selected value.
picker.set(value, silent = true)
- Type:
value: a date or range (same format asvalueoption)silent: iftrue, suppressesonPick
- What it does:
Sets the selected date(s) manually.
Useful for reactive updates or resets.
picker.set({ day: 12, month: 9, year: 2025 });picker.format(formatString)
- Returns:
- A string (in single mode)
- An array of strings (in range mode)
- What it does:
Formats the selected date(s) as strings using the specified format.
See Localization for available tokens.
picker.format('dd/mm/YYYY'); // → "12/09/2025"picker.focus()
- What it does:
Opens the picker and focuses the root element.
Use this if you want to open the picker programmatically.
Once opened programmatically, the picker will remain open until you callblur()manually or the user clicks the Apply button.
picker.blur()
- What it does:
Closes the picker and removes focus.
picker.nextMonth(callback?)
- What it does:
Moves the view to the next calendar month.
Callscallback()after animation completes (optional).
RespectsminDate/maxDateboundaries.
picker.prevMonth(callback?)
- What it does:
Moves the view to the previous calendar month.
Callscallback()after animation completes (optional).
RespectsminDate/maxDateboundaries.
picker.goTo({ year, month }, callback?)
- What it does:
Jumps the view to a specific month/year and runscallbackafter the transition.
This method does not clamp tominDate/maxDate.
picker.goTo({ year: 2026, month: 1 }, () => {
console.log('Now showing January 2026');
});picker.selectPeriod(periodName, silent = true)
What it does:
Selects one of the built-in presets by name (e.g.,"today","last7days").
Usesilent: falseto triggeronPick.Available period names:
'today''yesterday''last7days''last30days''last90days''thisWeek''thisMonth''thisYear''lastWeek''lastMonth''lastYear''nextWeek''nextMonth''nextYear''custom'
picker.selectPeriod('last30days');picker.startLoading()
- What it does:
Visually disables the picker and shows a loading shimmer.
Useful if you're waiting for external data.
picker.stopLoading()
- What it does:
Hides the loading state and makes the picker interactive again.
picker.error(message, focus = false)
- What it does:
Shows an error message below the picker.
Iffocusistrue, the picker will also open.
picker.error('Invalid range', true);picker.clearError()
- What it does:
Removes any visible error message.
picker.destroy()
- What it does:
Completely removes the picker and all attached listeners.
Call this if you need to clean up.
Localization (i18n)
The date picker supports multiple languages and custom labels. You can choose from built-in locales or override the text yourself.
Built-in locales
You can set the locale option to one of the supported language codes:
'en'- English (default)'fr'- French'es'- Spanish'de'- German'uk'- Ukrainian'ar'- Arabic'it'- Italian'pt'- Portuguese'zh'- Chinese'ja'- Japanese
new DatePicker(container, {
locale: 'de'
});This updates all buttons, labels, month names, weekdays, and presets.
Custom translations with trans
You can override any label using the trans option. This works with or without setting locale.
new DatePicker(container, {
trans: {
apply: 'OK',
today: 'Now',
last7days: 'Past 7 Days'
}
});Available keys you can override:
{
clear: 'Clear',
apply: 'Apply',
today: 'Today',
yesterday: 'Yesterday',
last7days: 'Last 7 days',
last30days: 'Last 30 days',
last90days: 'Last 90 days',
thisWeek: 'This week',
thisMonth: 'This month',
thisYear: 'This year',
lastWeek: 'Last week',
lastMonth: 'Last month',
lastYear: 'Last year',
nextWeek: 'Next Week',
nextMonth: 'Next Month',
nextYear: 'Next Year',
custom: 'Custom',
weekdays: [...], // e.g. ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
weekDaysShort: [...], // e.g. ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
weekDaysLong: [...], // e.g. ['Sunday', 'Monday', ...]
monthsShort: [...], // e.g. ['Jan', 'Feb', ...]
monthsLong: [...] // e.g. ['January', 'February', ...]
}If you're customizing any of the array fields (weekdays, weekDaysShort, weekDaysLong, monthsShort, monthsLong), make sure your arrays:
- Have the same number of items as the default (7 for weekdays, 12 for months)
- Are in the correct order:
- Weekdays start from Sunday (
Su,Mo,Tu, ...) - Months start from January (
Jan,Feb, ...)
- Weekdays start from Sunday (
This ensures the calendar grid stays correct.
Formatting dates with .format()
The .format() method lets you convert the selected date(s) into a string, using tokens like dd, mm, YYYY, etc.
picker.format('dd/mm/YYYY'); // → "17/10/2025"This does not change how dates are displayed inside the picker - it’s only for formatting output when you need a specific string format (e.g., to send in a request or show somewhere else).
To control the display inside the picker, use the format option when creating it.
Supported tokens for .format()
| Token | Meaning | Example |
|---|---|---|
d |
Day (no zero) | 5 |
dd |
Day (2 digits) | 05 |
m |
Month (no zero) | 9 |
mm |
Month (2 digits) | 09 |
M |
Month short name | Sep |
MM |
Month full name | September |
YY |
2-digit year | 25 |
YYYY |
4-digit year | 2025 |
D |
Weekday short name | Tue |
DD |
Weekday full name | Tuesday |
These tokens are localized automatically based on the selected locale or your custom trans object.
Accessibility
The date picker is designed to be fully accessible out of the box - no extra configuration needed.
Keyboard navigation
Users can navigate and interact using only the keyboard:
Arrow keys: move across days, or navigate preset periods (in range mode)EnterorSpace: select a dateEscape: close the pickerTabandShift+Tab: move between focusable areas
In range mode, the keyboard highlights update dynamically as users move.
Screen reader support
The picker uses semantic HTML roles and ARIA attributes to provide meaningful information to screen readers:
- The main wrapper uses
role="button"andaria-expandedto indicate state - The calendar grid uses
role="grid"androle="gridcell"for day cells - Selected and focused states use
aria-selectedappropriately - All buttons and icons have accessible labels
- Month/year controls are exposed as focusable UI elements and announce the current visible month and year.
There’s also a live region (aria-live="polite") to announce updates when months or values change.
Recipes
Here are some useful patterns you can apply when using the picker in real projects.
Controlled value (update manually)
You can update the selected date programmatically at any time using .set():
const picker = new DatePicker(container, { mode: 'single' });
// Later in your code
picker.set({ day: 10, month: 12, year: 2025 });In range mode:
picker.set([
{ day: 1, month: 10, year: 2025 },
{ day: 15, month: 10, year: 2025 }
]);Jump to a specific month/year
You can open the calendar directly at a target month:
const picker = new DatePicker(container, { mode: 'single' });
// Jump to January 2026 (view only)
picker.goTo({ year: 2026, month: 1 });Clear selection
To clear the picker value manually:
picker.set([null, null]); // works for both single and rangeShow error state
You can show a temporary error message below the picker:
picker.error('Invalid date selected');You can remove it with:
picker.clearError();Format date(s) for display or requests
If you need to send a formatted string somewhere (e.g., for a search filter), use .format():
const value = picker.format('YYYY-mm-dd');
// Returns string in single mode or array in range modeThis doesn’t affect how dates appear inside the picker - it’s just for output.
Set a preset programmatically
You can select one of the built-in presets with .selectPeriod():
picker.selectPeriod('last7days');This will update the value and UI instantly. You can pass false to trigger onPick.
Limit date range to the past 30 days
new DatePicker(container, {
mode: 'range',
maxRange: 30,
maxDate: { day: 1, month: 11, year: 2025 }
});This ensures users can’t select more than 30 days, and not beyond November 1st, 2025.
Framework Usage
The date picker works with any frontend framework - or no framework at all.
It doesn't rely on global DOM access, jQuery, or custom lifecycle hooks.
You create it by passing in a DOM element and options.
It can be used in React, Vue, Svelte, or plain HTML with no wrappers.
Vanilla JavaScript
const container = document.querySelector('#picker');
const picker = new DatePicker(container, {
mode: 'single',
onApply: (date) => {
console.log('Picked date:', date);
}
});React (useEffect example)
import { useEffect, useRef } from 'react';
import { DatePicker } from '@ragyjs/date-picker';
import '@ragyjs/date-picker/style.css';
function DatePickerComponent() {
const ref = useRef(null);
const pickerRef = useRef(null);
useEffect(() => {
pickerRef.current = new DatePicker(ref.current, {
mode: 'range',
onApply: (range) => {
console.log(range);
}
});
return () => {
pickerRef.current?.destroy();
};
}, []);
return <div ref={ref} />;
}Vue (with onMounted)
<template>
<div ref="pickerEl"></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { DatePicker } from '@ragyjs/date-picker';
import '@ragyjs/date-picker/style.css';
const pickerEl = ref();
let picker;
onMounted(() => {
picker = new DatePicker(pickerEl.value, {
mode: 'single',
onApply: (date) => {
console.log(date);
}
});
});
onBeforeUnmount(() => {
picker?.destroy();
});
</script>Notes
- Make sure to call
.destroy()when your component unmounts, especially in React/Vue/Svelte apps. - You can use multiple instances on the same page - each is isolated.
Performance
The picker is lightweight and doesn’t ship any third-party libraries.
- There are no re-renders - the component only updates when needed
- DOM updates are minimal and scoped to what changes (no full redraws)
- It’s safe to use multiple instances on the same page
If you're building complex or high-traffic UIs, the picker stays responsive and cheap to run - even when embedded in large dashboards or filters.
FAQ / Troubleshooting
The picker renders but looks broken or unstyled
Make sure you’ve imported the required CSS file:
import '@ragyjs/date-picker/style.css';The picker relies on CSS variables for layout and theming. Without this file, it won't display correctly.
Dates look off by one day
Make sure you're using the correct date format:
{ day: 5, month: 10, year: 2025 } // month is 1-basedMonth numbers start from 1 (January = 1, December = 12).
The picker doesn't open when I click it
Ensure that the root container is in the document and focusable.
If you're using custom styles or animation wrappers, check that the .rjs-datePicker is visible and not inside a hidden/inert element.
Also make sure you're not disabling focus by accident (e.g., missing tabindex).
I want to disable weekends or specific days
This feature isn’t built-in yet, but you can use CSS or logic inside onPick() to detect and handle blocked dates manually.
Support for disabledDates and disabledWeekdays is planned.
How do I format the selected date?
Use .format() to return the selected date(s) as a string:
picker.format('YYYY-mm-dd');This does not change the display inside the picker.
To change how the date appears inside the UI, use the format option.
Can I use it without a framework?
Yes - it works with plain JavaScript.
Just pass a DOM element and some options:
new DatePicker(document.querySelector('#picker'), { mode: 'single' });Can I have more than one picker on the page?
Yes - multiple pickers are fully supported. Each instance is isolated and independent.
How do I remove a picker when I’m done?
Call .destroy() on the instance:
picker.destroy();This will remove all DOM listeners and references.
License
This project is open source and available under the MIT License.
You can use it freely in personal or commercial projects, modify it, or redistribute it - just keep the original license file and attribution.
For full details, see the LICENSE file.
Support
If you find this project helpful, please consider sponsoring me on GitHub.
Even small contributions help keep the project active and maintained.