BW DatePicker
Zero-dependency datepicker built for engineers who care about craft. Production-ready. Plugin-powered. Framework-agnostic.
Live Examples
These are actual BW DatePicker instances running on this page. Click on dates, navigate months, and interact with them.
Light Theme (Inline)
Always-visible calendar with light theme. Select dates directly.
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
const picker = new BWDatePicker('#date-input', {
mode: 'inline',
container: '#calendar-container'
}).use(ThemingPlugin, { theme: 'light' });
picker.on('date:changed', ({ date }) => {
console.log('Selected:', date);
});
Dark Theme (Inline)
Black & white monochrome dark theme. The signature BW look.
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
const picker = new BWDatePicker('#date-input', {
mode: 'inline'
}).use(ThemingPlugin, { theme: 'dark' });
Theme Comparison
Side-by-side light and dark themes for direct comparison.
// Light theme
const lightPicker = new BWDatePicker('#light-input', {
mode: 'inline'
}).use(ThemingPlugin, { theme: 'light' });
// Dark theme
const darkPicker = new BWDatePicker('#dark-input', {
mode: 'inline'
}).use(ThemingPlugin, { theme: 'dark' });
With Date Constraints
Limit selectable dates with min/max. Past dates are disabled.
const picker = new BWDatePicker('#date-input', {
mode: 'inline',
minDate: new Date(), // Today onwards only
maxDate: new Date('2026-12-31')
});
Popup Mode
Click the input field to open the calendar as a popup.
const picker = new BWDatePicker('#date-input', {
mode: 'popup', // Default mode
closeOnSelect: true
});
picker.on('date:changed', ({ date }) => {
console.log('Selected:', date);
});
Overview
Most datepickers are bloated, hard to customize, or locked into a framework. BW DatePicker is different — it respects your bundle size, works anywhere, and lets you use only what you need through its powerful plugin architecture.
Core is only ~27KB gzipped — add only what you need
10+ official plugins for theming, accessibility, mobile & more
Dark mode, light mode, auto-detect, CSS variables
WCAG 2.1 compliant, full keyboard navigation
Touch gestures, swipe navigation, responsive design
No external libraries. No supply chain risk.
Installation
npm / yarn / pnpm
# 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-positioning
CDN
No build step required. Add directly to your HTML:
<!-- Styles -->
<link rel="stylesheet" href="https://unpkg.com/@bw-ui/datepicker/dist/bw-datepicker.min.css">
<!-- Script -->
<script src="https://unpkg.com/@bw-ui/datepicker/dist/bw-datepicker.min.js"></script>
Available Packages
| Package | Description | Size |
|---|---|---|
@bw-ui/datepicker |
Core datepicker engine | ~27.3KB |
@bw-ui/datepicker-react |
React components & hooks | ~5.1KB |
@bw-ui/datepicker-vue |
Vue 3 components | ~4.8KB |
Quick Start
Basic Usage (3 lines)
import { 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.
With Plugins (Production Setup)
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' })
.use(AccessibilityPlugin)
.use(PositioningPlugin, { autoFlip: true })
.use(MobilePlugin);
// Listen for changes
picker.on('date:changed', ({ date, dateISO }) => {
console.log('Selected:', dateISO);
});
Usage Examples
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
const picker = new BWDatePicker('#date-input', {
mode: 'popup',
format: 'YYYY-MM-DD'
}).use(ThemingPlugin, { theme: 'dark' });
picker.on('date:changed', ({ 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>
import { 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"
/>
);
}
With Date Constraints
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
]
});
Options
Pass options as the second argument to the constructor:
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 |
Methods
Lifecycle
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 datepicker
Date Operations
picker.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 date
Navigation
picker.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 date
Plugin Management
picker.use(Plugin, options); // Add a plugin
picker.hasPlugin('name'); // Check if plugin is loaded
picker.getPlugin('name'); // Get plugin instance
Events
Subscribe to events using .on():
picker.on('eventName', (data) => { /* handler */ });
Date Events
| Event | Payload | Description |
|---|---|---|
date:changed |
{ date, dateISO, oldDate } |
Date was changed |
date:selected |
{ date, dateISO } |
User selected a date |
date:cleared |
{} |
Date was cleared |
Lifecycle Events
| Event | Payload | Description |
|---|---|---|
picker:opened |
{} |
Datepicker was opened |
picker:closed |
{} |
Datepicker was closed |
picker:destroyed |
{} |
Instance was destroyed |
Navigation Events
| Event | Payload | Description |
|---|---|---|
nav:monthChanged |
{ month, year } |
Month was changed |
nav:yearChanged |
{ year } |
Year was changed |
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}`);
});
Plugins
BW DatePicker has a fully modular plugin architecture. Install only what you need.
Theming Plugin
Dark mode, light mode, and auto-detection with full CSS variable support.
import { 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
darkClass: 'dark' // CSS class for dark mode
});
Accessibility Plugin
Full keyboard navigation, ARIA attributes, and screen reader support. WCAG 2.1 compliant.
import { AccessibilityPlugin } from '@bw-ui/datepicker-accessibility';
picker.use(AccessibilityPlugin);
// Keyboard shortcuts enabled:
// ← → ↑ ↓ Navigate days
// Enter Select date
// Escape Close picker
// PageUp Previous month
// PageDown Next month
Positioning Plugin
Smart popup positioning with collision detection and auto-flip when near viewport edges.
import { PositioningPlugin } from '@bw-ui/datepicker-positioning';
picker.use(PositioningPlugin, {
placement: 'bottom-start', // Where to position
autoFlip: true, // Flip if no space
offset: [0, 8] // [x, y] offset
});
// Available placements:
// top, top-start, top-end
// bottom, bottom-start, bottom-end
// left, left-start, left-end
// right, right-start, right-end
Mobile Plugin
Touch gestures, swipe navigation, and mobile-optimized interactions.
import { MobilePlugin } from '@bw-ui/datepicker-mobile';
picker.use(MobilePlugin, {
swipeThreshold: 50, // Min swipe distance (px)
preventScroll: true // Lock body scroll when open
});
// Features:
// • Swipe left/right for months
// • Touch-optimized hit areas
// • Prevents body scroll
Range Plugin
Select a date range with start and end dates. Visual highlighting included.
import { RangePlugin } from '@bw-ui/datepicker-range';
picker.use(RangePlugin, {
presets: ['today', 'last7days', 'last30days', 'thisMonth'],
presetsPosition: 'left'
});
picker.on('range:complete', ({ startDate, endDate, nights }) => {
console.log('Check-in:', startDate);
console.log('Check-out:', endDate);
console.log('Nights:', nights);
});
Multidate Plugin
Select multiple individual dates. Perfect for booking multiple days.
import { MultidatePlugin } from '@bw-ui/datepicker-multidate';
picker.use(MultidatePlugin, {
maxDates: 5, // Maximum selections
separator: ', ' // Display separator
});
// Get all selected dates
const dates = picker.getDates(); // Returns Date[]
Locale Plugin
Internationalization and translations for different languages and regions.
import { LocalePlugin } from '@bw-ui/datepicker-locale';
picker.use(LocalePlugin, {
locale: 'de-DE',
firstDayOfWeek: 1, // Monday
translations: {
today: 'Heute',
clear: 'Löschen',
close: 'Schließen'
}
});
Input Handler Plugin
Manual input with masking, validation, and format enforcement as you type.
import { InputHandlerPlugin } from '@bw-ui/datepicker-input-handler';
picker.use(InputHandlerPlugin, {
format: 'DD/MM/YYYY',
allowManualInput: true,
mask: true, // Auto-format as typing
strictMode: false // Allow partial dates
});
Date Utils Plugin
Natural language date parsing and advanced manipulation.
import { DateUtilsPlugin } from '@bw-ui/datepicker-date-utils';
picker.use(DateUtilsPlugin);
// Parse natural language
picker.parseDate('tomorrow');
picker.parseDate('next friday');
picker.parseDate('in 2 weeks');
picker.parseDate('dec 25');
picker.parseDate('end of month');
Dual Calendar Plugin
Side-by-side month view, perfect for selecting date ranges.
import { DualCalendarPlugin } from '@bw-ui/datepicker-dual-calendar';
import { RangePlugin } from '@bw-ui/datepicker-range';
picker
.use(DualCalendarPlugin, { months: 2, linked: true })
.use(RangePlugin);
// Perfect for hotel bookings, flights, etc.
Create Your Own Plugin
BW DatePicker has a fully open plugin architecture. Build anything you need.
Plugin Structure
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 */ });
The API Object
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
}
Example: Highlight Today Plugin
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%';
}
};
eventBus.on('picker:opened', highlightToday);
eventBus.on('nav:monthChanged', highlightToday);
return { color, highlightToday };
}
};
// Usage
picker.use(HighlightTodayPlugin, { color: '#10b981' });
React Integration
Use BW DatePicker in your React application with a simple wrapper component. The vanilla JS core ensures minimal bundle size while React handles the component lifecycle.
import { useEffect, useRef } from 'react';
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
import { RangePlugin } from '@bw-ui/datepicker-range';
import '@bw-ui/datepicker/dist/bw-datepicker.min.css';
import '@bw-ui/datepicker-theming/dist/bw-theming.min.css';
import '@bw-ui/datepicker-range/dist/bw-range.min.css';
interface DatePickerProps {
onChange?: (date: Date | null) => void;
onRangeChange?: (start: Date, end: Date) => void;
theme?: 'light' | 'dark';
enableRange?: boolean;
placeholder?: string;
}
export function DatePicker({
onChange,
onRangeChange,
theme = 'light',
enableRange = false,
placeholder = 'Select date'
}: DatePickerProps) {
const inputRef = useRef<HTMLInputElement>(null);
const pickerRef = useRef<BWDatePicker | null>(null);
useEffect(() => {
if (!inputRef.current) return;
// Initialize picker
const picker = new BWDatePicker(inputRef.current, {
mode: 'popup',
format: 'YYYY-MM-DD'
});
// Apply plugins
picker.use(ThemingPlugin, { theme });
if (enableRange) {
picker.use(RangePlugin, {
presets: ['today', 'last7days', 'thisMonth']
});
picker.on('range:complete', (e) => {
onRangeChange?.(e.startDate, e.endDate);
});
} else {
picker.on('date:changed', (e) => {
onChange?.(e.date);
});
}
pickerRef.current = picker;
// Cleanup on unmount
return () => {
picker.destroy();
};
}, [theme, enableRange]);
return (
<input
ref={inputRef}
type="text"
placeholder={placeholder}
className="bw-datepicker-input"
/>
);
}
import { DatePicker } from './DatePicker';
function BookingForm() {
const handleDateChange = (date: Date | null) => {
console.log('Selected:', date);
};
const handleRangeChange = (start: Date, end: Date) => {
console.log('Check-in:', start);
console.log('Check-out:', end);
};
return (
<div>
{/* Single date picker */}
<DatePicker
onChange={handleDateChange}
theme="light"
placeholder="Pick a date"
/>
{/* Range picker for bookings */}
<DatePicker
enableRange
onRangeChange={handleRangeChange}
theme="dark"
placeholder="Select dates"
/>
</div>
);
}
Vue Integration
Integrate BW DatePicker into Vue 3 applications using the Composition API. The wrapper component handles initialization and cleanup automatically.
<!-- BWDatePicker.vue -->
<template>
<input
ref="inputRef"
type="text"
:placeholder="placeholder"
class="bw-datepicker-input"
/>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { BWDatePicker } from '@bw-ui/datepicker';
import { ThemingPlugin } from '@bw-ui/datepicker-theming';
import { RangePlugin } from '@bw-ui/datepicker-range';
import '@bw-ui/datepicker/dist/bw-datepicker.min.css';
import '@bw-ui/datepicker-theming/dist/bw-theming.min.css';
import '@bw-ui/datepicker-range/dist/bw-range.min.css';
interface Props {
theme?: 'light' | 'dark';
enableRange?: boolean;
placeholder?: string;
}
const props = withDefaults(defineProps<Props>(), {
theme: 'light',
enableRange: false,
placeholder: 'Select date'
});
const emit = defineEmits<{
change: [date: Date | null];
rangeChange: [start: Date, end: Date];
}>();
const inputRef = ref<HTMLInputElement | null>(null);
let picker: BWDatePicker | null = null;
const initPicker = () => {
if (!inputRef.value) return;
picker = new BWDatePicker(inputRef.value, {
mode: 'popup',
format: 'YYYY-MM-DD'
});
picker.use(ThemingPlugin, { theme: props.theme });
if (props.enableRange) {
picker.use(RangePlugin, {
presets: ['today', 'last7days', 'thisMonth']
});
picker.on('range:complete', (e) => {
emit('rangeChange', e.startDate, e.endDate);
});
} else {
picker.on('date:changed', (e) => {
emit('change', e.date);
});
}
};
onMounted(() => {
initPicker();
});
onUnmounted(() => {
picker?.destroy();
});
// Re-initialize when theme changes
watch(() => props.theme, () => {
picker?.destroy();
initPicker();
});
</script>
<!-- BookingForm.vue -->
<template>
<div class="booking-form">
<!-- Single date picker -->
<BWDatePicker
@change="handleDateChange"
theme="light"
placeholder="Pick a date"
/>
<!-- Range picker for bookings -->
<BWDatePicker
enable-range
@range-change="handleRangeChange"
theme="dark"
placeholder="Select dates"
/>
</div>
</template>
<script setup lang="ts">
import BWDatePicker from './BWDatePicker.vue';
const handleDateChange = (date: Date | null) => {
console.log('Selected:', date);
};
const handleRangeChange = (start: Date, end: Date) => {
console.log('Check-in:', start);
console.log('Check-out:', end);
};
</script>
Browser Support
| Browser | Version |
|---|---|
| Chrome | 60+ |
| Firefox | 60+ |
| Safari | 12+ |
| Edge | 79+ |
Comparison
| Feature | BW DatePicker | react-datepicker | flatpickr |
|---|---|---|---|
| Zero dependencies | ✅ | ❌ | ✅ |
| Modular plugins | ✅ | ❌ | ❌ |
| Framework agnostic | ✅ | ❌ | ✅ |
| React bindings | ✅ | ✅ | ❌ |
| Full keyboard nav | ✅ | ⚠️ | ✅ |
| Mobile swipe | ✅ | ❌ | ❌ |
| Custom plugins | ✅ | ❌ | ⚠️ |
| WCAG 2.1 | ✅ | ⚠️ | ⚠️ |
| Core size (gzipped) | ~27KB | ~40KB | ~16KB |