Menu

Date picker

A date picker lets users pick a date from a calendar instead of typing it, for booking forms, deadlines and other date fields.

Overview

The date picker is Tabler's Datepicker plugin wrapped around Vanilla Calendar Pro. It is the same plugin, with the same options, methods and events, as the Datepicker in Bootstrap 6, so markup written for one works with the other. Tabler styles the calendar the plugin renders, so it follows your theme, your border radius and your dark mode.

Add data-bs-toggle="datepicker" to a text input. The calendar opens when the field gets focus, and the picked date is written back into the field, so it works in a form like any other text field.

<label class="form-label" for="datepicker-overview">Date</label>
<input type="text" class="form-control" id="datepicker-overview" data-bs-toggle="datepicker" autocomplete="off" placeholder="Select a date" />

Installation

The plugin itself ships in tabler.js. The calendar it renders comes from Vanilla Calendar Pro, which you load separately. Install it with npm:

npm install vanilla-calendar-pro
yarn add vanilla-calendar-pro
pnpm install vanilla-calendar-pro
bun install vanilla-calendar-pro

If you use a bundler, put the library on window before Tabler loads. The plugin reads window.VanillaCalendarPro and does nothing without it. Imports run before the rest of a file, so do it in a file of its own and import that one first:

// vanilla-calendar-global.js
import * as VanillaCalendarPro from 'vanilla-calendar-pro'

window.VanillaCalendarPro = VanillaCalendarPro
import './vanilla-calendar-global.js'
import '@tabler/core'

Or include it from a CDN, before tabler.js:

<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/libs/vanilla-calendar-pro/index.js"></script>

You do not need the library's own stylesheet. Tabler styles the calendar in tabler.css.

Vanilla Calendar Pro 3.4 splits several months, the time picker, week numbers, popups and animation into extensions. Tabler registers every extension the loaded library exports, so options such as data-bs-display-months-count="2" work without importing them yourself.

Usage

Basic input

Use type="text", not type="date", so the browser's own picker does not open too. Add autocomplete="off" so the browser does not cover the calendar with old values.

<label class="form-label" for="datepicker-basic">Date</label>
<input type="text" class="form-control" id="datepicker-basic" data-bs-toggle="datepicker" autocomplete="off" placeholder="Select a date" />

Icon input

Wrap the field in .input-icon and add a calendar icon, so users can see what the field is for at a glance. The popup lines up with the wrapper, not with the input inside it.

<label class="form-label" for="datepicker-icon">Date</label>
<div class="input-icon">
  <input type="text" class="form-control" id="datepicker-icon" data-bs-toggle="datepicker" autocomplete="off" placeholder="Select a date" />
  <span class="input-icon-addon">
    <!-- Download SVG icon from http://tabler.io/icons/icon/calendar -->
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon">
      <path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12" />
      <path d="M16 3v4" />
      <path d="M8 3v4" />
      <path d="M4 11h16" />
      <path d="M11 15h1" />
      <path d="M12 15v3" />
    </svg>
  </span>
</div>

Initial value

A value in YYYY-MM-DD format is shown on load and the calendar opens on that month. You can also preselect dates with data-bs-selected-dates, which takes a JSON array.

<label class="form-label" for="datepicker-value">Birth date</label>
<input type="text" class="form-control" id="datepicker-value" data-bs-toggle="datepicker" autocomplete="off" value="1990-06-20" />

Min and max dates

Limit the pickable dates with data-bs-date-min and data-bs-date-max. Dates outside the range are shown but cannot be picked.

<label class="form-label" for="datepicker-min-max">Event date (2026 only)</label>
<input type="text" class="form-control" id="datepicker-min-max" data-bs-toggle="datepicker" autocomplete="off" data-bs-date-min="2026-01-01" data-bs-date-max="2026-12-31" placeholder="Select a date in 2026" />

Date range

Set data-bs-selection-mode="multiple-ranged" to pick a start and an end date. The calendar closes after the second date. The field shows both dates with a dash between them.

<label class="form-label" for="datepicker-range">Stay</label>
<input type="text" class="form-control" id="datepicker-range" data-bs-toggle="datepicker" autocomplete="off" data-bs-selection-mode="multiple-ranged" data-bs-selected-dates="[&quot;2026-06-10&quot;, &quot;2026-06-18&quot;]" placeholder="Select start and end" />

Multiple dates

Set data-bs-selection-mode="multiple" to pick any number of dates. The calendar stays open until focus leaves it. The field lists the dates with commas.

<label class="form-label" for="datepicker-multiple">Days off</label>
<input type="text" class="form-control" id="datepicker-multiple" data-bs-toggle="datepicker" autocomplete="off" data-bs-selection-mode="multiple" placeholder="Select dates" />

Two months

Add data-bs-display-months-count="2" to show two months side by side. This helps with ranges that cross a month boundary.

<label class="form-label" for="datepicker-two-months">Stay</label>
<input type="text" class="form-control" id="datepicker-two-months" data-bs-toggle="datepicker" autocomplete="off" data-bs-selection-mode="multiple-ranged" data-bs-display-months-count="2" placeholder="Select start and end" />

First day of the week

The week starts on Monday. Set data-bs-first-weekday="0" to start it on Sunday.

<label class="form-label" for="datepicker-sunday">Date</label>
<input type="text" class="form-control" id="datepicker-sunday" data-bs-toggle="datepicker" autocomplete="off" data-bs-first-weekday="0" placeholder="Select a date" />

Week numbers

Add data-bs-week-numbers="true" to show the week number in front of each row. Use it where people plan by week, for example delivery dates or sprints.

<label class="form-label" for="datepicker-week-numbers">Delivery date</label>
<input type="text" class="form-control" id="datepicker-week-numbers" data-bs-toggle="datepicker" autocomplete="off" data-bs-week-numbers="true" placeholder="Select a date" />

Placement

The popup lines up with the left edge of the field. Set data-bs-placement to center, right or auto to change that.

<label class="form-label" for="datepicker-right">Right aligned</label>
<input type="text" class="form-control" id="datepicker-right" data-bs-toggle="datepicker" autocomplete="off" data-bs-placement="right" placeholder="Select a date" />

Date format

The field shows the date in the format of data-bs-locale. Set data-bs-date-format to a pattern to write it the same way in every language. The pattern uses YYYY for the year, MM and M for the month, and DD and D for the day. The short tokens drop the leading zero. Everything else is copied as it is. Use iso as a shortcut for YYYY-MM-DD.

<div class="row g-3">
  <div class="col-sm-6">
    <label class="form-label" for="datepicker-format-iso">ISO date, Polish calendar</label>
    <input type="text" class="form-control" id="datepicker-format-iso" data-bs-toggle="datepicker" autocomplete="off" data-bs-locale="pl-PL" data-bs-date-format="iso" value="2026-09-30" />
  </div>
  <div class="col-sm-6">
    <label class="form-label" for="datepicker-format-pattern">Custom pattern</label>
    <input type="text" class="form-control" id="datepicker-format-pattern" data-bs-toggle="datepicker" autocomplete="off" data-bs-date-format="DD/MM/YYYY" placeholder="DD/MM/YYYY" />
  </div>
</div>

A field with a pattern also reads its value in that pattern, so a form the server sends back keeps the picked date selected.

Button trigger

Any element can open the calendar, not only an input. A button shows the picked date as its text. Put the text in a child with data-bs-datepicker-display so an icon next to it stays.

<div class="btn-list">
  <button type="button" class="btn" data-bs-toggle="datepicker">
    <!-- Download SVG icon from http://tabler.io/icons/icon/calendar -->
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon">
      <path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12" />
      <path d="M16 3v4" />
      <path d="M8 3v4" />
      <path d="M4 11h16" />
      <path d="M11 15h1" />
      <path d="M12 15v3" />
    </svg>
    <span data-bs-datepicker-display="true">Select date</span>
  </button>
  <button type="button" class="btn" data-bs-toggle="datepicker" data-bs-selection-mode="multiple-ranged">
    <!-- Download SVG icon from http://tabler.io/icons/icon/calendar-event -->
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" class="icon">
      <path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12" />
      <path d="M16 3l0 4" />
      <path d="M8 3l0 4" />
      <path d="M4 11l16 0" />
      <path d="M8 15h2v2h-2l0 -2" />
    </svg>
    <span data-bs-datepicker-display="true">Select range</span>
  </button>
</div>

To show the date somewhere else, pass the displayElement option when you create the picker in JavaScript.

Inline calendar

Add data-bs-inline="true" to render the calendar in place instead of a popup. Tabler creates inline calendars on page load, so they need no extra JavaScript. An inline calendar has no background or border of its own. Put it in a card to give it a surface.

<div class="card" style="width: fit-content">
  <div class="card-body">
    <div data-bs-toggle="datepicker" data-bs-inline="true"></div>
  </div>
</div>

Inline calendars take the same options as popups:

<div data-bs-toggle="datepicker" data-bs-inline="true" data-bs-selection-mode="multiple-ranged" data-bs-display-months-count="2"></div>

To submit the picked dates with a form, put a hidden input inside the calendar. Tabler writes the dates to it in YYYY-MM-DD format, separated by commas.

<form onsubmit="return false">
  <div data-bs-toggle="datepicker" data-bs-inline="true">
    <input type="hidden" name="selected_date" />
  </div>
  <button type="submit" class="btn btn-primary mt-3">Submit</button>
</form>

A popup field works the same way. Point data-bs-bound-input at a hidden input and give the name to that input, not to the field. The field shows the date in the visitor's language, and the form sends YYYY-MM-DD. When the hidden input already has a value, the picker selects that date on page load.

<form onsubmit="return false">
  <label class="form-label" for="datepicker-bound">Due date</label>
  <input type="text" class="form-control" id="datepicker-bound" data-bs-toggle="datepicker" autocomplete="off" data-bs-locale="pl-PL" data-bs-bound-input="#datepicker-bound-value" placeholder="Select a date" />
  <input type="hidden" id="datepicker-bound-value" name="due_date" value="2026-09-30" />
  <button type="submit" class="btn btn-primary mt-3">Submit</button>
</form>

JavaScript

Via data attributes

Add data-bs-toggle="datepicker" and Tabler does the rest. Inputs open on focus, other elements open on click, and inline calendars and fields with a value, data-bs-selected-dates or data-bs-bound-input render on page load. Every option below can be set as a data-bs-* attribute, for example data-bs-selection-mode="multiple".

EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (this: HTMLElement, event: Event) {
  // Inputs open on focus, and inline calendars are always visible
  if (this.tagName === 'INPUT' || this.dataset.bsInline === 'true' || this.dataset.tblrInline === 'true') {
    return
  }

  event.preventDefault()
  void (Datepicker.getOrCreateInstance(this) as Datepicker).toggle()
})

EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function (this: HTMLElement) {
  if (this.tagName !== 'INPUT') {
    return
  }

  void (Datepicker.getOrCreateInstance(this) as Datepicker).show()
})

// Render on load what cannot wait for a focus or a click: inline calendars,
// and fields with preselected dates or a value, so they show it right away.
initAll(SELECTOR_DATA_TOGGLE, Datepicker, (element) => {
  const { dataset } = element

  return dataset.bsInline === 'true' || dataset.tblrInline === 'true' || 'bsSelectedDates' in dataset || 'tblrSelectedDates' in dataset || 'bsBoundInput' in dataset || 'tblrBoundInput' in dataset || Boolean((element as HTMLInputElement).value)
})

Via JavaScript

Create the picker yourself when you need options that do not fit in an attribute, such as a date format or a callback:

const datepicker = new tabler.Datepicker(document.getElementById('my-date'), {
  selectionMode: 'multiple-ranged',
  firstWeekday: 0,
  dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
});

Options

Name Type Default Description
dateMin string, number, Date null First pickable date, as YYYY-MM-DD.
dateMax string, number, Date null Last pickable date, as YYYY-MM-DD.
boundInput string, element null Input that gets the picked dates as YYYY-MM-DD, separated by commas. An inline calendar uses a hidden input inside it.
dateFormat string, object, function null How the date is shown in the field. Pass a pattern such as 'DD.MM.YYYY', 'iso', Intl.DateTimeFormat options or a function(date, locale) that returns a string.
displayElement string, element, boolean null Element that shows the picked date. A button uses itself or its [data-bs-datepicker-display] child. Set false to show nothing.
displayMonthsCount number 1 Months shown side by side.
firstWeekday number 1 First day of the week: 0 is Sunday, 1 is Monday.
inline boolean false Render the calendar in place instead of a popup.
locale string 'default' Locale for month names and the date shown in the field, for example 'en-US' or 'de-DE'. A dateFormat pattern takes over the field.
positionElement string, element null Element the popup lines up with. Defaults to the .input-icon or .input-group wrapper, or the field itself.
selectedDates array [] Preselected dates as YYYY-MM-DD.
selectionMode string 'single' 'single', 'multiple' or 'multiple-ranged'.
placement string 'left' Where the popup lines up with the field: 'left', 'center', 'right' or 'auto'.
weekNumbers boolean false Show the week number in front of each row.
datepickerTheme string null Force the popup's theme to 'light', 'dark' or 'auto'. By default it follows the nearest [data-bs-theme].
vcpOptions object {} Any Vanilla Calendar Pro option, passed straight through.

Use vcpOptions for everything the plugin can do that Tabler does not expose, for example disabling weekends or adding a time picker:

const datepicker = new tabler.Datepicker(element, {
  vcpOptions: {
    disableDatesPast: true,
    disableWeekdays: [0, 6],
    selectionTimeMode: 24,
  },
});

Methods

Method Description
show() Opens the popup. Does nothing for an inline calendar.
hide() Closes the popup.
toggle() Opens or closes the popup.
getSelectedDates() Returns the picked dates as an array of YYYY-MM-DD strings.
setSelectedDates(dates) Sets the picked dates. Takes an array of YYYY-MM-DD strings.
dispose() Removes the calendar and the instance.
calendar The Vanilla Calendar Pro instance, for anything the methods above do not cover.
Datepicker.getInstance(element) Returns the instance for an element, or null.
Datepicker.getOrCreateInstance(element) Returns the instance, or creates one.

Events

Event Description
show.bs.datepicker Fires before the popup opens. Call preventDefault() to keep it closed.
shown.bs.datepicker Fires once the popup is open.
hide.bs.datepicker Fires before the popup closes. Call preventDefault() to keep it open.
hidden.bs.datepicker Fires once the popup is closed.
change.bs.datepicker Fires when the user picks a date. The event has dates, an array of YYYY-MM-DD strings, and event, the click.
document.getElementById('my-date').addEventListener('change.bs.datepicker', (event) => {
  console.log(event.dates);
});

Accessibility

  • Always add a <label> linked to the input with for and id. A calendar icon alone does not say what the field is for.
  • Vanilla Calendar Pro marks the calendar up as a grid with role="grid", role="row" and role="gridcell", labels every date and handles the arrow keys. Give an inline calendar an accessible name with aria-label when nothing around it names it.
  • The popup closes on Escape, on a click outside, and when focus moves to something outside the calendar, so keyboard users are never trapped in it.
  • Users can still type a date into the field. The picker is a shortcut, not the only way to set the value.
  • The calendar respects prefers-reduced-motion and drops its animations for users who ask for that.

Migration from Litepicker

Tabler 1.5 and older used Litepicker, which has been archived since 2023. It is deprecated: dist/libs/litepicker and its styles in tabler-vendors.css stay until 2.0, so existing pages keep working. To move over, load Vanilla Calendar Pro instead and switch the markup as shown below.

Litepicker Datepicker
new Litepicker({ element }) data-bs-toggle="datepicker" on the element
inlineMode: true data-bs-inline="true"
singleMode: false data-bs-selection-mode="multiple-ranged"
minDate / maxDate data-bs-date-min / data-bs-date-max
numberOfMonths: 2 data-bs-display-months-count="2"
format: 'DD.MM.YYYY' data-bs-date-format="DD.MM.YYYY" (tokens YYYY, MM, M, DD, D)
picker.on('selected', …) change.bs.datepicker event

CSS

Sass variables

Override these variables before importing Tabler to change the defaults. Source: core/scss/_variables.scss.

$datepicker-calendar-padding: 0;
$datepicker-calendar-font-size: $font-size-sm;
$datepicker-calendar-header-font-weight: $font-weight-medium;
$datepicker-calendar-day-size: 2rem;
$datepicker-calendar-day-font-size: $font-size-sm;
$datepicker-calendar-range-bg-opacity: 10%;
$datepicker-padding: 1rem;
$datepicker-bg: $dropdown-bg;
$datepicker-border-radius: $dropdown-border-radius;
$datepicker-box-shadow: $dropdown-box-shadow;
$datepicker-min-width: 280px;