Production-ready. Plugin-powered. Framework-agnostic.
Live Demo Β· Quick Start Β· Packages Β· Plugins Β· API Β· Create Plugin
Most datepickers are bloated, hard to customize, or locked into a framework.
We built something different β a datepicker that respects your bundle size, works anywhere, and lets you use only what you need.
| Feature | BW DatePicker | react-datepicker | flatpickr |
|---|---|---|---|
| Zero dependencies | β | β | β |
| Modular plugins (use only what you need) | β | β | β |
| Works without React/Vue/Angular | β | β | β |
| Official React bindings | β | β | β |
| Full keyboard navigation | β | β | |
| Mobile swipe gestures | β | β | β |
| Create custom plugins | β | β | |
| WCAG 2.1 accessible | β | ||
| Core size (gzipped) | ~27KB | ~40KB | ~16KB |
| πͺΆ | Lightweight | Core is only ~27KB gzipped β add only what you need |
| π | Plugin Architecture | 10 official plugins β theming, accessibility, mobile, positioning & more |
| π¨ | Themeable | Dark mode, light mode, auto-detect, CSS variables, fully customizable |
| βΏ | Accessible | WCAG 2.1 compliant, full keyboard navigation, screen reader support |
| π± | Mobile Ready | Touch gestures, swipe navigation, responsive design |
| π | Zero Dependencies | No external libraries. No supply chain risk. Just pure code. |
| π¦ | Multiple Formats | ESM, IIFE, CommonJS β works everywhere |
| π§© | Extensible | Create and publish your own plugins with full API access |
| βοΈ | Framework Support | Official React bindings, Vue wrapper coming soon |
| π· | TypeScript | Written in TypeScript, full type definitions included |
The ecosystem is fully modular. Install the core and add only the plugins you need.
| Package | Description | Size | Links |
|---|---|---|---|
@bw-ui/datepicker |
Core datepicker engine | ~27.3KB |
| Package | Description | Size | Links |
|---|---|---|---|
@bw-ui/datepicker-react |
React components & hooks | ~5.1KB | |
@bw-ui/datepicker-vue |
Vue 3 components | ~4.8KB |
# Core only
npm install @bw-ui/datepicker
# With recommended plugins
npm install @bw-ui/datepicker @bw-ui/datepicker-theming @bw-ui/datepicker-accessibility @bw-ui/datepicker-positioningimport { BWDatePicker } from '@bw-ui/datepicker';
import '@bw-ui/datepicker/dist/bw-datepicker.css';
const picker = new BWDatePicker('#date-input');That's it. No config required. It just works.
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
import { AccessibilityPlugin } from '@bw-ui/datepicker-accessibility';
import { PositioningPlugin } from '@bw-ui/datepicker-positioning';
import { MobilePlugin } from '@bw-ui/datepicker-mobile';
// Styles
import '@bw-ui/datepicker/dist/bw-datepicker.css';
import '@bw-ui/datepicker-theming/dist/bw-theming.css';
// Create picker with plugins
const picker = new BWDatePicker('#date-input', {
mode: 'popup',
format: 'YYYY-MM-DD',
minDate: new Date(),
})
.use(ThemingPlugin, { theme: 'auto' }) // Auto dark/light mode
.use(AccessibilityPlugin) // Full keyboard nav
.use(PositioningPlugin, { autoFlip: true }) // Smart positioning
.use(MobilePlugin); // Touch gestures
// Listen for changes
picker.on('date:changed', ({ date, dateISO }) => {
console.log('Selected:', dateISO);
});import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
import { AccessibilityPlugin } from '@bw-ui/datepicker-accessibility';
const picker = new BWDatePicker('#date-input', {
mode: 'popup',
format: 'YYYY-MM-DD',
})
.use(ThemingPlugin, { theme: 'dark' })
.use(AccessibilityPlugin);
picker.on('date:changed', ({ date, dateISO }) => {
console.log('Selected:', dateISO);
});<!-- Styles -->
<link
rel="stylesheet"
href="https://unpkg.com/@bw-ui/datepicker/dist/bw-datepicker.min.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/@bw-ui/datepicker-theming/dist/bw-theming.min.css"
/>
<!-- Scripts -->
<script src="https://unpkg.com/@bw-ui/datepicker/dist/bw-datepicker.min.js"></script>
<script src="https://unpkg.com/@bw-ui/datepicker-theming/dist/bw-theming.min.js"></script>
<!-- Your HTML -->
<input type="text" id="date-input" placeholder="Select a date" />
<!-- Initialize -->
<script>
const picker = new BW.BWDatePicker('#date-input').use(
BWTheming.ThemingPlugin,
{ theme: 'dark' }
);
picker.on('date:changed', function (e) {
console.log('Selected:', e.dateISO);
});
</script>npm install @bw-ui/datepicker @bw-ui/datepicker-react @bw-ui/datepicker-themingimport { useState } from 'react';
import { BWDatePicker } from '@bw-ui/datepicker-react';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
// Styles
import '@bw-ui/datepicker/dist/bw-datepicker.css';
import '@bw-ui/datepicker-theming/dist/bw-theming.css';
function App() {
const [date, setDate] = useState(null);
return (
<BWDatePicker
value={date}
onChange={setDate}
plugins={[ThemingPlugin]}
pluginOptions={{
theming: { theme: 'dark' },
}}
placeholder="Select a date"
format="YYYY-MM-DD"
/>
);
}
export default App;π Full React Documentation β
const picker = new BWDatePicker('#date', {
mode: 'popup',
format: 'DD/MM/YYYY',
// Only allow future dates
minDate: new Date(),
// Until end of next year
maxDate: new Date('2026-12-31'),
// Block specific dates (holidays, etc.)
disabledDates: [
new Date('2025-01-01'), // New Year
new Date('2025-12-25'), // Christmas
new Date('2025-12-26'), // Boxing Day
],
});new BWDatePicker(selector, options?)| Option | Type | Default | Description |
|---|---|---|---|
mode |
'popup' | 'modal' | 'inline' |
'popup' |
How the datepicker is displayed |
format |
string |
'YYYY-MM-DD' |
Date format for display and input |
minDate |
Date | null |
null |
Minimum selectable date |
maxDate |
Date | null |
null |
Maximum selectable date |
disabledDates |
Date[] |
[] |
Array of dates that cannot be selected |
firstDayOfWeek |
0-6 |
0 |
First day of week (0 = Sunday) |
closeOnSelect |
boolean |
true |
Close picker after date selection |
picker.open(); // Open the datepicker
picker.close(); // Close the datepicker
picker.toggle(); // Toggle open/close
picker.destroy(); // Destroy instance and cleanup
picker.refresh(); // Re-render the datepickerpicker.setDate(date); // Set the selected date
picker.getDate(); // Get the selected date (Date object)
picker.getDateISO(); // Get date as ISO string
picker.clear(); // Clear the selection
picker.today(); // Set to today's datepicker.prevMonth(); // Navigate to previous month
picker.nextMonth(); // Navigate to next month
picker.prevYear(); // Navigate to previous year
picker.nextYear(); // Navigate to next year
picker.goToDate(date); // Navigate to specific datepicker.use(Plugin, options); // Add a plugin
picker.hasPlugin('name'); // Check if plugin is loaded
picker.getPlugin('name'); // Get plugin instanceSubscribe to events using .on():
picker.on('eventName', (data) => {
/* handler */
});| Event | Payload | Description |
|---|---|---|
date:changed |
{ date, dateISO, oldDate } |
Date was changed (programmatically or by user) |
date:selected |
{ date, dateISO } |
User selected a date |
date:cleared |
{} |
Date was cleared |
| Event | Payload | Description |
|---|---|---|
picker:opened |
{} |
Datepicker was opened |
picker:closed |
{} |
Datepicker was closed |
picker:destroyed |
{} |
Instance was destroyed |
| Event | Payload | Description |
|---|---|---|
nav:monthChanged |
{ month, year } |
Month was changed |
nav:yearChanged |
{ year } |
Year was changed |
| Event | Payload | Description |
|---|---|---|
render:header |
{ html } |
Header is being rendered |
render:calendar |
{ html } |
Calendar grid is being rendered |
render:footer |
{ html } |
Footer is being rendered |
Example:
const picker = new BWDatePicker('#date');
picker.on('date:changed', ({ date, dateISO, oldDate }) => {
console.log('New date:', dateISO);
console.log('Previous date:', oldDate);
});
picker.on('picker:opened', () => {
console.log('Datepicker is now open');
});
picker.on('nav:monthChanged', ({ month, year }) => {
console.log(`Viewing: ${month}/${year}`);
});Add dark mode, light mode, and auto-detection.
npm install @bw-ui/datepicker-themingimport { ThemingPlugin } from '@bw-ui/datepicker-theming';
import '@bw-ui/datepicker-theming/dist/bw-theming.css';
picker.use(ThemingPlugin, {
theme: 'auto', // 'light' | 'dark' | 'auto'
persist: true, // Remember preference in localStorage
darkClass: 'dark', // CSS class for dark mode
});Features:
- π Dark mode
- βοΈ Light mode
- π Auto-detect system preference
- πΎ Persist user preference
- π¨ CSS variables for full customization
Full keyboard navigation and screen reader support.
npm install @bw-ui/datepicker-accessibilityimport { AccessibilityPlugin } from '@bw-ui/datepicker-accessibility';
picker.use(AccessibilityPlugin);Keyboard Shortcuts:
| Key | Action |
|---|---|
β β β β |
Navigate between days |
Enter / Space |
Select focused date |
Escape |
Close datepicker |
Tab |
Move focus |
PageUp |
Previous month |
PageDown |
Next month |
Home |
First day of month |
End |
Last day of month |
Smart popup positioning with collision detection.
npm install @bw-ui/datepicker-positioningimport { PositioningPlugin } from '@bw-ui/datepicker-positioning';
picker.use(PositioningPlugin, {
placement: 'bottom-start', // Where to position
autoFlip: true, // Flip if not enough space
offset: [0, 8], // [x, y] offset in pixels
});Placement Options:
top,top-start,top-endbottom,bottom-start,bottom-endleft,left-start,left-endright,right-start,right-end
Touch gestures and mobile optimizations.
npm install @bw-ui/datepicker-mobileimport { MobilePlugin } from '@bw-ui/datepicker-mobile';
picker.use(MobilePlugin, {
swipeThreshold: 50, // Minimum swipe distance
preventScroll: true, // Prevent body scroll when open
});Features:
- π Swipe left/right for month navigation
- π Touch-optimized hit areas
- π Prevents body scroll when picker is open
- π± Responsive design
Manual input, masking, and validation.
npm install @bw-ui/datepicker-input-handlerimport { InputHandlerPlugin } from '@bw-ui/datepicker-input-handler';
picker.use(InputHandlerPlugin, {
format: 'DD/MM/YYYY',
allowManualInput: true,
mask: true, // Auto-format as user types
strictMode: false, // Allow partial dates
});Advanced date parsing and manipulation.
npm install @bw-ui/datepicker-date-utilsimport { DateUtilsPlugin } from '@bw-ui/datepicker-date-utils';
picker.use(DateUtilsPlugin);
// Now you can use:
picker.parseDate('25 Dec 2025');
picker.parseDate('next friday');
picker.parseDate('in 2 weeks');Internationalization and translations.
npm install @bw-ui/datepicker-localeimport { LocalePlugin } from '@bw-ui/datepicker-locale';
picker.use(LocalePlugin, {
locale: 'de-DE',
translations: {
today: 'Heute',
clear: 'LΓΆschen',
// ... more
},
});Select multiple dates.
npm install @bw-ui/datepicker-multidateimport { MultidatePlugin } from '@bw-ui/datepicker-multidate';
picker.use(MultidatePlugin, {
maxDates: 5, // Maximum selections
separator: ', ', // Display separator
});
// Get all selected dates
picker.getDates(); // Returns Date[]BW DatePicker has a fully open plugin architecture. Build anything you need.
const MyPlugin = {
name: 'my-plugin', // Unique identifier
init(api, options) {
// Called when plugin is registered
// Return cleanup object (optional)
return {
/* instance data */
};
},
destroy(instance) {
// Called when datepicker is destroyed
// Clean up your plugin
},
};
// Usage
picker.use(MyPlugin, {
/* options */
});Your plugin receives full access to datepicker internals:
init(api, options) {
// Core instance
api.datepicker // The BWDatePicker instance
// DOM Elements
api.getPickerElement() // The picker container element
api.getInputElement() // The input element
// Systems
api.getEventBus() // Event system for listening/emitting
api.getStateManager() // Internal state management
// Configuration
api.getOptions() // User-provided options
}A simple plugin that highlights today's date with a custom color:
export const HighlightTodayPlugin = {
name: 'highlight-today',
init(api, options = {}) {
const eventBus = api.getEventBus();
const color = options.color || '#ff6b6b';
const highlightToday = () => {
const today = api
.getPickerElement()
?.querySelector('.bw-datepicker__day--today');
if (today) {
today.style.backgroundColor = color;
today.style.color = 'white';
today.style.borderRadius = '50%';
}
};
// Highlight on open and when calendar rerenders
eventBus.on('picker:opened', highlightToday);
eventBus.on('nav:monthChanged', highlightToday);
return { color, highlightToday };
},
destroy(instance) {
// Cleanup handled automatically
},
};
// Usage
picker.use(HighlightTodayPlugin, { color: '#10b981' });A more advanced plugin that adds date range selection:
export const DateRangePlugin = {
name: 'date-range',
init(api, options = {}) {
const eventBus = api.getEventBus();
let startDate = null;
let endDate = null;
// Handle date selections
eventBus.on('date:selected', ({ date }) => {
if (!startDate || (startDate && endDate)) {
// Start new range
startDate = date;
endDate = null;
highlightRange();
} else {
// Complete range
if (date < startDate) {
endDate = startDate;
startDate = date;
} else {
endDate = date;
}
highlightRange();
// Emit custom event
eventBus.emit('range:selected', {
start: startDate,
end: endDate,
});
}
});
function highlightRange() {
// Add visual highlighting logic here
}
// Extend datepicker API
api.datepicker.getRange = () => ({ start: startDate, end: endDate });
api.datepicker.clearRange = () => {
startDate = null;
endDate = null;
highlightRange();
};
return {
getRange: () => ({ start: startDate, end: endDate }),
};
},
destroy(instance) {
// Cleanup
},
};
// Usage
picker.use(DateRangePlugin);
picker.on('range:selected', ({ start, end }) => {
console.log(`Selected range: ${start} to ${end}`);
});
// Access range
const { start, end } = picker.getRange();Inject custom CSS:
export const CustomStylesPlugin = {
name: 'custom-styles',
init(api, options = {}) {
const style = document.createElement('style');
style.id = 'bw-datepicker-custom-styles';
style.textContent = `
.bw-datepicker__day--selected {
background: ${
options.selectedBg ||
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
} !important;
border-radius: ${options.borderRadius || '50%'} !important;
color: white !important;
}
.bw-datepicker__day:hover {
background: ${options.hoverBg || 'rgba(102, 126, 234, 0.1)'} !important;
}
`;
document.head.appendChild(style);
return { styleElement: style };
},
destroy(instance) {
instance.styleElement?.remove();
},
};
// Usage
picker.use(CustomStylesPlugin, {
selectedBg: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
borderRadius: '8px',
});// Lifecycle
'picker:opened'; // Picker was opened
'picker:closed'; // Picker was closed
'picker:destroyed'; // Instance destroyed
// Date
'date:changed'; // { date, dateISO, oldDate }
'date:selected'; // { date, dateISO }
'date:cleared'; // {}
// Navigation
'nav:monthChanged'; // { month, year }
'nav:yearChanged'; // { year }
// Render (intercept and modify HTML)
'render:header'; // { html }
'render:calendar'; // { html }
'render:footer'; // { html }- Create package.json:
{
"name": "@yourname/bw-datepicker-myplugin",
"version": "1.0.0",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"@bw-ui/datepicker": "^1.0.0"
},
"keywords": ["bw-datepicker", "datepicker", "plugin"]
}- Export your plugin:
// src/index.js
export const MyPlugin = {
/* ... */
};
export default MyPlugin;- Publish:
npm publish --access public| 60+ | 60+ | 12+ | 79+ |
# Clone the repository
git clone https://github.com/bw-ui/bw-datepicker.git
cd bw-datepicker
# Install dependencies
npm install
# Start development server
npm run dev
# Build all packages
npm run build
# Build specific package
npm run build:core
npm run build:theming
npm run build:accessibility
# Run tests
npm test
# Lint
npm run lintbw-datepicker/
βββ packages/
β βββ core/ # @bw-ui/datepicker
β βββ react/ # @bw-ui/datepicker-react
β βββ theming/ # @bw-ui/datepicker-theming
β βββ accessibility/ # @bw-ui/datepicker-accessibility
β βββ positioning/ # @bw-ui/datepicker-positioning
β βββ mobile/ # @bw-ui/datepicker-mobile
β βββ input-handler/ # @bw-ui/datepicker-input-handler
β βββ date-utils/ # @bw-ui/datepicker-date-utils
β βββ locale/ # @bw-ui/datepicker-locale
β βββ multidate/ # @bw-ui/datepicker-multidate
βββ docs/ # Documentation site
βββ examples/ # Example projects
βββ scripts/ # Build scripts
We welcome contributions! Whether it's:
- π Bug reports
- π‘ Feature requests
- π Documentation improvements
- π§ Code contributions
Please read our Contributing Guide before submitting a PR.
Quick Links:
MIT Β© BW UI
Built with β€οΈ by Black & White UI Engineering
If this project helped you, consider giving it a βοΈ
