npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

calkit

v0.2.1

Published

Vanilla JS web component library for date pickers, time pickers, booking calendars, and resource schedulers.

Readme

CalKit

Vanilla JS web component library for date pickers, time pickers, booking calendars, and resource schedulers. Zero dependencies. Shadow DOM encapsulated. Themeable.

Install

CDN (<script> tag)

<script src="https://cdn.jsdelivr.net/npm/calkit/dist/calkit.umd.js"></script>

All four components are registered automatically. No imports needed.

Bundler (Vite, Webpack, etc.)

import { CalDatepicker, CalBooking, CalTimepicker, CalScheduler } from 'calkit';

Uses the ES module builds from node_modules.

Individual Bundles

Load only the components you need:

| Bundle | CDN (<script>) | ES Module (bundler) | Gzipped | |--------|------------------|---------------------|---------| | Full (all 4) | calkit.umd.js (148 KB) | calkit.es.js (176 KB) | ~34 KB | | Datepicker only | datepicker.umd.js (48 KB) | datepicker.es.js (56 KB) | ~12 KB | | Timepicker only | timepicker.umd.js (32 KB) | timepicker.es.js (36 KB) | ~8 KB | | Booking only | booking.umd.js (56 KB) | booking.es.js (64 KB) | ~14 KB | | Scheduler only | scheduler.umd.js (88 KB) | scheduler.es.js (104 KB) | ~21 KB |


Quick Start

Datepicker

<cal-datepicker mode="single" theme="light"></cal-datepicker>

<script>
  const picker = document.querySelector('cal-datepicker');
  picker.addEventListener('cal:change', (e) => {
    console.log('Selected:', e.detail.value); // "2026-03-15"
  });
</script>

Timepicker

<cal-timepicker start-time="09:00" end-time="17:00" interval="30" format="12h"></cal-timepicker>

<script>
  const tp = document.querySelector('cal-timepicker');
  tp.addEventListener('cal:time-change', (e) => {
    console.log('Selected:', e.detail.value); // "14:30"
  });
</script>

Booking Calendar

<cal-booking theme="light"></cal-booking>

<script>
  const booking = document.querySelector('cal-booking');
  booking.bookings = [
    { id: '1', start: '2026-03-10', end: '2026-03-14', label: 'Alice', color: 'blue' },
    { id: '2', start: '2026-03-14', end: '2026-03-18', label: 'Bob', color: 'green' },
  ];
  booking.addEventListener('cal:change', (e) => {
    console.log('Booked:', e.detail.value); // { start, end }
  });
</script>

Scheduler

<cal-scheduler view="week" start-time="08:00" end-time="18:00" theme="light"></cal-scheduler>

<script>
  const sched = document.querySelector('cal-scheduler');
  sched.resources = [
    { id: 'room-a', name: 'Room A' },
    { id: 'room-b', name: 'Room B' },
  ];
  sched.events = [
    { id: '1', title: 'Meeting', start: '2026-03-02', startTime: '09:00', endTime: '10:30', resourceId: 'room-a', color: 'blue' },
  ];
  sched.addEventListener('cal:slot-select', (e) => {
    console.log('Slot:', e.detail); // { date, startTime, endTime, resourceId, resource }
  });
</script>

Components Overview

| Feature | Datepicker | Timepicker | Booking | Scheduler | |---------|:----------:|:----------:|:-------:|:---------:| | Selection modes | single, multi, range | single, multi, range | range | slot-based | | Display modes | inline, popover | inline, popover | inline, popover | inline | | Views | — | — | — | day, week, month | | Theming | light, dark, auto | light, dark, auto | light, dark, auto | light, dark, auto | | Loading skeleton | yes | yes | yes | yes | | Keyboard navigation | yes | — | yes | Escape to dismiss | | Drag interaction | — | — | — | move, resize, create | | Resources | — | — | — | tabs, columns |


cal-datepicker

A date picker supporting single, multi, and range selection with inline or popover display.

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | mode | "single" | "multi" | "range" | "single" | Selection mode | | display | "inline" | "popover" | "inline" | Render mode | | theme | "light" | "dark" | "auto" | "light" | Color theme | | value | string | — | Initial value. Single: "2026-03-15". Range: "2026-03-10/2026-03-15". Multi: "2026-03-10,2026-03-12" | | min-date | string | — | Earliest selectable date ("YYYY-MM-DD") | | max-date | string | — | Latest selectable date ("YYYY-MM-DD") | | disabled-dates | string | — | Comma-separated dates to disable ("2026-03-25,2026-03-26") | | first-day | number | 0 | First day of week (0 = Sunday, 1 = Monday) | | locale | string | — | Locale for formatting | | presets | string | — | Comma-separated preset keys for range mode: "today,this-week,next-7,next-30" | | placeholder | string | "Select date" | Popover trigger placeholder text | | dual | boolean | — | Show two months side-by-side (range mode) | | loading | boolean | — | Show skeleton loading state |

Properties

| Property | Type | Description | |----------|------|-------------| | value | string \| string[] \| {start: string, end: string} \| null | Read/write. Shape depends on mode | | loading | boolean | Read/write loading state |

Methods

| Method | Parameters | Description | |--------|------------|-------------| | open() | — | Open popover (popover mode only) | | close() | — | Close popover | | goToMonth(month, year) | month: number (0-11), year: number | Navigate view to specific month | | showStatus(type, message, opts?) | type: "error"\|"warning"\|"info"\|"success" | Show status banner | | clearStatus() | — | Clear status banner |

Events

| Event | detail shape | Fires when | |-------|---------------|------------| | cal:change | {value: string} (single) / {value: {start, end}} (range) / {value: string[]} (multi) | Date selection changes | | cal:month-change | {year: number, month: number} | View navigates to new month | | cal:open | {} | Popover opens | | cal:close | {} | Popover closes | | cal:status | {type: string\|null, message: string\|null} | Status banner changes |

Example: Inline Range with Presets

<cal-datepicker
  mode="range"
  dual
  presets="today,this-week,next-7,next-30"
  min-date="2026-01-01"
  theme="light"
></cal-datepicker>

<script>
  document.querySelector('cal-datepicker').addEventListener('cal:change', (e) => {
    const { start, end } = e.detail.value;
    console.log(`Range: ${start} to ${end}`);
  });
</script>

Example: Popover with Initial Value

<cal-datepicker
  mode="single"
  display="popover"
  value="2026-03-15"
  placeholder="Pick a date"
></cal-datepicker>

Full reference: docs/cal-datepicker.md


cal-timepicker

A time slot picker for selecting single times, multiple times, or time ranges.

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | mode | "single" | "multi" | "range" | "single" | Selection mode | | display | "inline" | "popover" | "inline" | Render mode | | theme | "light" | "dark" | "auto" | "light" | Color theme | | start-time | string | "09:00" | First slot time ("HH:MM") | | end-time | string | "17:00" | Last slot boundary ("HH:MM") | | interval | number | 30 | Minutes between slots | | format | "12h" | "24h" | "24h" | Time display format | | value | string | — | Initial value. Single: "14:30". Range: "09:00/12:00". Multi: "09:00,10:30" | | placeholder | string | "Select time" | Popover trigger placeholder | | duration-labels | boolean | — | Show duration labels on each slot | | loading | boolean | — | Show skeleton loading state |

Properties

| Property | Type | Description | |----------|------|-------------| | value | string \| string[] \| {start: string, end: string} \| null | Read/write. Shape depends on mode | | slots | Array<{time: string, label?: string, available?: boolean}> | Custom slot definitions (overrides auto-generation) | | unavailableTimes | string[] | Array of "HH:MM" strings to mark unavailable | | loading | boolean | Read/write loading state |

Methods

| Method | Parameters | Description | |--------|------------|-------------| | open() | — | Open popover | | close() | — | Close popover | | showStatus(type, message, opts?) | type: "error"\|"warning"\|"info"\|"success" | Show status banner | | clearStatus() | — | Clear status banner |

Events

| Event | detail shape | Fires when | |-------|---------------|------------| | cal:time-change | {value: string} (single) / {value: {start, end}} (range) / {value: string[]} (multi) | Time selection changes | | cal:open | {} | Popover opens | | cal:close | {} | Popover closes | | cal:status | {type: string\|null, message: string\|null} | Status banner changes |

Example: Duration Labels

<cal-timepicker
  start-time="09:00"
  end-time="17:00"
  interval="60"
  format="12h"
  duration-labels
></cal-timepicker>

Example: Custom Slots with Unavailable Times

<cal-timepicker mode="single" format="12h"></cal-timepicker>

<script>
  const tp = document.querySelector('cal-timepicker');
  tp.slots = [
    { time: '09:00', label: 'Morning', available: true },
    { time: '12:00', label: 'Lunch', available: false },
    { time: '14:00', label: 'Afternoon', available: true },
  ];
</script>

Full reference: docs/cal-timepicker.md


cal-booking

A booking calendar that displays existing reservations as colored overlays and lets users select date ranges. Supports overlap validation and optional time slot selection.

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | theme | "light" | "dark" | "auto" | "light" | Color theme | | display | "inline" | "popover" | "inline" | Render mode | | min-date | string | — | Earliest selectable date | | max-date | string | — | Latest selectable date | | first-day | number | 0 | First day of week | | placeholder | string | "Select dates" | Popover trigger placeholder | | dual | boolean | — | Show two months side-by-side | | show-labels-on-hover | boolean | — | Show booking labels on day hover | | time-slots | boolean | — | Enable time slot selection after date range | | time-start | string | "09:00" | Time grid start (when time-slots enabled) | | time-end | string | "17:00" | Time grid end | | time-interval | number | 60 | Time grid interval in minutes | | time-format | "12h" | "24h" | "24h" | Time display format | | duration-labels | boolean | — | Show duration labels on time slots | | loading | boolean | — | Show skeleton loading state |

Properties

| Property | Type | Description | |----------|------|-------------| | value | {start: string, end: string, startTime?: string, endTime?: string} \| null | Read/write selected range | | bookings | Booking[] | Array of existing bookings to display | | dayData | Record<string, {label?: string, status?: string}> | Per-date metadata | | labelFormula | (dateStr: string) => {label?: string, status?: string} \| null | Dynamic label function (highest priority) | | timeSlots | Array<{time: string, label?: string, available?: boolean}> | Custom time slot definitions | | loading | boolean | Read/write loading state |

Methods

| Method | Parameters | Description | |--------|------------|-------------| | open() | — | Open popover | | close() | — | Close popover | | goToMonth(month, year) | month: number (0-11), year: number | Navigate to month | | showStatus(type, message, opts?) | type: "error"\|"warning"\|"info"\|"success" | Show status banner | | clearStatus() | — | Clear status banner |

Events

| Event | detail shape | Fires when | |-------|---------------|------------| | cal:change | {value: {start, end, startTime?, endTime?}} | Range selection completes | | cal:selection-invalid | {start: string, end: string} | Selection overlaps existing booking | | cal:month-change | {year: number, month: number} | View navigates to new month | | cal:open | {} | Popover opens | | cal:close | {} | Popover closes | | cal:status | {type: string\|null, message: string\|null} | Status banner changes |

Example: Booking Calendar with Bookings

<cal-booking theme="light" dual></cal-booking>

<script>
  const booking = document.querySelector('cal-booking');
  booking.bookings = [
    { id: '1', start: '2026-03-05', end: '2026-03-10', label: 'Alice', color: 'blue' },
    { id: '2', start: '2026-03-10', end: '2026-03-15', label: 'Bob', color: 'green' },
    { id: '3', start: '2026-03-20', end: '2026-03-25', label: 'Carol', color: 'orange' },
  ];

  // Price labels via formula
  booking.labelFormula = (date) => {
    const d = new Date(date);
    const isWeekend = d.getDay() === 0 || d.getDay() === 6;
    return { label: isWeekend ? '$150' : '$100' };
  };

  booking.addEventListener('cal:change', (e) => {
    console.log('Selected range:', e.detail.value);
  });

  booking.addEventListener('cal:selection-invalid', () => {
    console.log('Overlaps existing booking!');
  });
</script>

Example: Date + Time Flow

<cal-booking
  time-slots
  time-start="14:00"
  time-end="22:00"
  time-interval="30"
  time-format="12h"
></cal-booking>

<script>
  document.querySelector('cal-booking').addEventListener('cal:change', (e) => {
    // { start: "2026-03-10", end: "2026-03-12", startTime: "14:00", endTime: "11:00" }
    console.log(e.detail.value);
  });
</script>

Full reference: docs/cal-booking.md


cal-scheduler

A full-featured resource scheduling calendar with day, week, and month views. Supports drag-to-move, drag-to-resize, drag-to-create, resource tabs/columns, all-day events, and a floating action button.

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | theme | "light" | "dark" | "auto" | "light" | Color theme | | view | "day" | "week" | "month" | "week" | Current calendar view | | layout | "vertical" | "vertical" | Grid layout | | date | string | today | Anchor date ("YYYY-MM-DD") | | start-time | string | "08:00" | Day grid start time | | end-time | string | "18:00" | Day grid end time | | interval | number | 30 | Slot interval in minutes | | format | "12h" | "24h" | "24h" | Time display format | | first-day | number | 0 | First day of week | | slot-height | number | 48 | Pixel height per time slot | | resource-mode | "tabs" | "columns" | "tabs" | How resources are displayed | | show-event-time | "true" | "false" | "true" | Show time row on event blocks | | show-fab | boolean | — | Show floating action button | | draggable-events | boolean | — | Enable drag-to-move/resize/create | | snap-interval | number | — | Drag snap interval in minutes (defaults to interval) | | min-duration | number | — | Minimum event duration in minutes for drag operations | | max-duration | number | — | Maximum event duration in minutes for drag operations | | loading | boolean | — | Show skeleton loading state |

Properties

| Property | Type | Description | |----------|------|-------------| | resources | Resource[] | Array of resource objects | | events | Event[] | Array of event objects | | eventActions | EventAction[] | Action buttons shown in event detail popover | | eventContent | (event: Event, resource: Resource) => HTMLElement \| string | Custom event block renderer | | value | {date, startTime, endTime, resourceId, resource} \| null | Read-only last selected slot | | loading | boolean | Read/write loading state |

Methods

| Method | Parameters | Description | |--------|------------|-------------| | goToDate(dateStr) | dateStr: string | Navigate to date | | setView(view) | view: "day"\|"week"\|"month" | Switch view | | today() | — | Navigate to today | | next() | — | Navigate forward (day/week/month) | | prev() | — | Navigate backward | | findAvailableSlot(opts) | {date?, duration, resourceId?, minCapacity?} | Find first available slot across resources | | isSlotAvailable(date, startTime, endTime, resourceId) | all string | Check if a specific slot is free | | showStatus(type, message, opts?) | type: "error"\|"warning"\|"info"\|"success" | Show status banner | | clearStatus() | — | Clear status banner |

Events

| Event | detail shape | Fires when | |-------|---------------|------------| | cal:slot-select | {date, startTime, endTime, resourceId, resource} | Empty slot is clicked | | cal:slot-create | {date, startTime, endTime, resourceId, resource} | Drag-to-create completes | | cal:event-click | {event, resourceId, resource} | Event block is clicked | | cal:event-move | {event, from: {date, startTime, endTime, resourceId}, to: {date, startTime, endTime, resourceId}} | Drag-to-move completes | | cal:event-resize | {event, from: {endTime}, to: {endTime}} | Drag-to-resize completes | | cal:event-action | {action: string, event, resourceId, resource} | Event detail action button clicked | | cal:fab-create | {date: string, view: string} | FAB button clicked | | cal:date-change | {date: string, view: string} | Navigation changes date | | cal:view-change | {view: string, date: string} | View type changes | | cal:status | {type: string\|null, message: string\|null} | Status banner changes |

Example: Week View with Resources

<cal-scheduler
  view="week"
  resource-mode="tabs"
  start-time="08:00"
  end-time="18:00"
  format="12h"
  theme="light"
></cal-scheduler>

<script>
  const sched = document.querySelector('cal-scheduler');

  sched.resources = [
    { id: 'room-a', name: 'Room A', capacity: 10 },
    { id: 'room-b', name: 'Room B', capacity: 20 },
  ];

  sched.events = [
    {
      id: '1', title: 'Team Standup',
      start: '2026-03-02', startTime: '09:00', endTime: '09:30',
      resourceId: 'room-a', color: 'blue',
    },
    {
      id: '2', title: 'Workshop',
      start: '2026-03-03', startTime: '13:00', endTime: '16:00',
      resourceId: 'room-b', color: 'green',
    },
  ];

  sched.eventActions = [
    { label: 'Edit' },
    { label: 'Delete', type: 'danger' },
  ];

  sched.addEventListener('cal:event-action', (e) => {
    console.log(e.detail.action, e.detail.event);
  });
</script>

Example: Drag-to-Move and Resize

<cal-scheduler
  view="week"
  draggable-events
  snap-interval="15"
  min-duration="15"
  max-duration="240"
></cal-scheduler>

<script>
  const sched = document.querySelector('cal-scheduler');
  // ... set resources and events ...

  sched.addEventListener('cal:event-move', (e) => {
    const { event, from, to } = e.detail;
    console.log(`Moved "${event.title}" from ${from.date} ${from.startTime} to ${to.date} ${to.startTime}`);
    // Update your data and re-assign sched.events
  });

  sched.addEventListener('cal:event-resize', (e) => {
    const { event, from, to } = e.detail;
    console.log(`Resized "${event.title}" end: ${from.endTime} → ${to.endTime}`);
  });

  sched.addEventListener('cal:slot-create', (e) => {
    console.log('New event via drag:', e.detail);
    // { date, startTime, endTime, resourceId, resource }
  });
</script>

Example: Venue Booking with Availability Check

<cal-scheduler view="day" show-fab></cal-scheduler>

<script>
  const sched = document.querySelector('cal-scheduler');

  sched.resources = [
    { id: 'court-1', name: 'Court 1', capacity: 4 },
    { id: 'court-2', name: 'Court 2', capacity: 4 },
  ];

  // Find first available 60-minute slot
  const slot = sched.findAvailableSlot({ duration: 60 });
  if (slot) {
    console.log(`Available: ${slot.date} ${slot.startTime}-${slot.endTime} on ${slot.resourceId}`);
  }

  // Check specific slot
  const free = sched.isSlotAvailable('2026-03-02', '10:00', '11:00', 'court-1');

  sched.addEventListener('cal:fab-create', (e) => {
    console.log('Create new event for', e.detail.date);
  });
</script>

Full reference: docs/cal-scheduler.md


Theming

All components support three theme modes via the theme attribute:

| Value | Behavior | |-------|----------| | "light" (default) | Light color scheme | | "dark" | Dark color scheme | | "auto" | Follows prefers-color-scheme media query |

Top CSS Custom Properties

Override from outside the Shadow DOM using the tag selector:

| Token | Light Default | Description | |-------|--------------|-------------| | --cal-bg | 0 0% 100% | Background color (HSL channels) | | --cal-fg | 240 6% 10% | Foreground/text color | | --cal-accent | 240 6% 10% | Accent color for selected states | | --cal-border | 240 6% 90% | Border color | | --cal-radius | 8px | Border radius |

Override Example

cal-datepicker, cal-scheduler {
  --cal-accent: 220 90% 56%;
  --cal-accent-fg: 0 0% 100%;
  --cal-radius: 12px;
}

All color tokens use raw HSL channels (e.g., 240 6% 10%) and are consumed internally as hsl(var(--cal-...)). This allows alpha modifications with the / syntax: hsl(var(--cal-accent) / 0.5).

Full token table: docs/theming.md


Data Shapes

interface Event {
  id: string;
  title: string;
  start: string;             // "YYYY-MM-DD"
  end?: string;              // "YYYY-MM-DD" (multi-day or all-day)
  startTime?: string;        // "HH:MM" (omit for all-day events)
  endTime?: string;          // "HH:MM"
  resourceId?: string;       // links to Resource.id
  color?: "blue" | "green" | "red" | "orange" | "gray";
  locked?: boolean;          // prevents drag operations
  metadata?: Record<string, string | number>;
}

interface Resource {
  id: string;
  name: string;
  capacity?: number;
  color?: "blue" | "green" | "red" | "orange" | "gray";
}

interface Booking {
  id: string;
  start: string;             // "YYYY-MM-DD"
  end: string;               // "YYYY-MM-DD"
  label?: string;
  color?: "blue" | "green" | "red" | "orange" | "gray";
}

interface TimeSlot {
  time: string;              // "HH:MM"
  label?: string;            // display label
  available?: boolean;       // default: true
}

interface DayData {
  [dateStr: string]: {
    label?: string;
    status?: string;         // "available" | "booked" | custom
  };
}

interface EventAction {
  label: string;
  type?: "danger";           // red styling
}

Framework Integration

CalKit components are standard web components. They work in any framework.

React

import 'calkit';

function App() {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    el.events = [/* ... */];

    const handler = (e) => console.log(e.detail);
    el.addEventListener('cal:change', handler);
    return () => el.removeEventListener('cal:change', handler);
  }, []);

  return <cal-datepicker ref={ref} mode="single" theme="light" />;
}

Vue

<template>
  <cal-datepicker
    ref="picker"
    mode="range"
    theme="light"
    @cal:change="onChange"
  />
</template>

<script setup>
import 'calkit';

function onChange(e) {
  console.log(e.detail.value);
}
</script>

Svelte

<script>
  import 'calkit';

  function handleChange(e) {
    console.log(e.detail.value);
  }
</script>

<cal-datepicker mode="single" theme="light" on:cal:change={handleChange} />

Browser Support

CalKit requires Constructable Stylesheets (adoptedStyleSheets). Fallback <style> injection is included for older browsers.

| Browser | Version | |---------|---------| | Chrome | 73+ | | Edge | 79+ | | Firefox | 101+ | | Safari | 16.4+ |


License

MIT