Skip to content

πŸ—“οΈ Lightweight, zero-dependency datepicker with modular plugin architecture. ~27.3KB core, dark mode, keyboard navigation, mobile gestures.

License

Notifications You must be signed in to change notification settings

bw-ui/bw-datepicker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

BW DatePicker

Zero-dependency datepicker built for engineers who care about craft.

Production-ready. Plugin-powered. Framework-agnostic.


npm version bundle size license downloads zero dependencies typescript

Live Demo Β· Quick Start Β· Packages Β· Plugins Β· API Β· Create Plugin


BW DatePicker Demo




πŸ€” Why BW DatePicker?

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 βœ… ⚠️ Partial βœ…
Mobile swipe gestures βœ… ❌ ❌
Create custom plugins βœ… ❌ ⚠️ Limited
WCAG 2.1 accessible βœ… ⚠️ Partial ⚠️ Partial
Core size (gzipped) ~27KB ~40KB ~16KB



✨ Features

πŸͺΆ 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



πŸ“¦ Packages

The ecosystem is fully modular. Install the core and add only the plugins you need.


Core

Package Description Size Links
@bw-ui/datepicker Core datepicker engine ~27.3KB npm

Framework Bindings

Package Description Size Links
@bw-ui/datepicker-react React components & hooks ~5.1KB npm
@bw-ui/datepicker-vue Vue 3 components ~4.8KB npm

Official Plugins

Package Description Size Links
@bw-ui/datepicker-theming Dark mode, light mode, CSS variables ~11.5KB npm
@bw-ui/datepicker-accessibility Keyboard navigation, ARIA, screen readers ~18.8KB npm
@bw-ui/datepicker-positioning Smart popup placement, auto-flip, collision ~11.2KB npm
@bw-ui/datepicker-mobile Touch gestures, swipe navigation ~7.5KB npm
@bw-ui/datepicker-input-handler Input masking, manual typing, validation ~12.2KB npm
@bw-ui/datepicker-date-utils Date parsing, formatting, manipulation ~19KB npm
@bw-ui/datepicker-locale i18n, localization, translations ~8KB npm
@bw-ui/datepicker-multidate Multiple date selection ~8KB npm
@bw-ui/datepicker-range Date range selection ~9KB npm
@bw-ui/datepicker-dual-calendar Side-by-side month view ~10KB npm
@bw-ui/datepicker-data Data binding & state management ~6KB npm



πŸš€ Quick Start

Installation

# 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

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' }) // 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);
});



πŸ“– Usage Examples

ES Modules

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);
});

Browser (CDN) β€” No Build Step Required

<!-- 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>

React

npm install @bw-ui/datepicker @bw-ui/datepicker-react @bw-ui/datepicker-theming
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"
    />
  );
}

export default App;

πŸ“š Full React Documentation β†’


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
  ],
});



πŸ“š API Reference

Constructor

new BWDatePicker(selector, options?)

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 (programmatically or by user)
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

Render Events (for plugins)

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}`);
});



πŸ”Œ Official Plugins

Theming Plugin

Add dark mode, light mode, and auto-detection.

npm install @bw-ui/datepicker-theming
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 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 Documentation β†’


Accessibility Plugin

Full keyboard navigation and screen reader support.

npm install @bw-ui/datepicker-accessibility
import { 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

πŸ“š Full Documentation β†’


Positioning Plugin

Smart popup positioning with collision detection.

npm install @bw-ui/datepicker-positioning
import { 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-end
  • bottom, bottom-start, bottom-end
  • left, left-start, left-end
  • right, right-start, right-end

πŸ“š Full Documentation β†’


Mobile Plugin

Touch gestures and mobile optimizations.

npm install @bw-ui/datepicker-mobile
import { 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

πŸ“š Full Documentation β†’


Input Handler Plugin

Manual input, masking, and validation.

npm install @bw-ui/datepicker-input-handler
import { 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
});

πŸ“š Full Documentation β†’


Date Utils Plugin

Advanced date parsing and manipulation.

npm install @bw-ui/datepicker-date-utils
import { 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');

πŸ“š Full Documentation β†’


Locale Plugin

Internationalization and translations.

npm install @bw-ui/datepicker-locale
import { LocalePlugin } from '@bw-ui/datepicker-locale';

picker.use(LocalePlugin, {
  locale: 'de-DE',
  translations: {
    today: 'Heute',
    clear: 'LΓΆschen',
    // ... more
  },
});

πŸ“š Full Documentation β†’


Multidate Plugin

Select multiple dates.

npm install @bw-ui/datepicker-multidate
import { MultidatePlugin } from '@bw-ui/datepicker-multidate';

picker.use(MultidatePlugin, {
  maxDates: 5, // Maximum selections
  separator: ', ', // Display separator
});

// Get all selected dates
picker.getDates(); // Returns Date[]

πŸ“š Full Documentation β†’




🧩 Creating 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

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' });

Example: Date Range Plugin

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();

Example: Custom Styles Plugin

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',
});

Available Events for Plugins

// 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 }

Publishing Your Plugin

  1. 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"]
}
  1. Export your plugin:
// src/index.js
export const MyPlugin = {
  /* ... */
};
export default MyPlugin;
  1. Publish:
npm publish --access public



🌐 Browser Support

Chrome Chrome Firefox Firefox Safari Safari Edge Edge
60+ 60+ 12+ 79+



πŸ—οΈ Development

# 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 lint

Project Structure

bw-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



🀝 Contributing

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:




πŸ“„ License

MIT Β© BW UI




Built with ❀️ by Black & White UI Engineering

GitHub stars

If this project helped you, consider giving it a ⭐️