Flatpickr
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { format, isDate } from 'date-fns';import flatpickr from 'flatpickr';import 'flatpickr/dist/flatpickr.css';import { editorFactory } from 'handsontable/editors';import { rendererFactory } from 'handsontable/renderers';
registerAllModules();const DATE_FORMAT_US = 'MM/dd/yyyy';const DATE_FORMAT_EU = 'dd/MM/yyyy';
/* start:skip-in-preview */const data = [ { product: 'Dashboard Pro', category: 'Analytics', version: '3.2.0', releaseDate: '2025-03-15', status: 'Released', downloads: 12450, }, { product: 'Form Builder', category: 'Tools', version: '2.1.0', releaseDate: '2025-04-22', status: 'Released', downloads: 8320, }, { product: 'Chart Engine', category: 'Analytics', version: '4.0.0', releaseDate: '2025-06-10', status: 'Beta', downloads: 3100, }, { product: 'Auth Module', category: 'Security', version: '1.5.2', releaseDate: '2025-07-01', status: 'Released', downloads: 15600, }, { product: 'File Manager', category: 'Storage', version: '2.0.0', releaseDate: '2025-08-18', status: 'Planned', downloads: 0, }, { product: 'Email Service', category: 'Communication', version: '3.1.0', releaseDate: '2025-09-05', status: 'Released', downloads: 9870, }, { product: 'Search Index', category: 'Tools', version: '1.2.0', releaseDate: '2025-10-12', status: 'Beta', downloads: 2450, }, { product: 'Cache Layer', category: 'Infra', version: '2.3.1', releaseDate: '2025-11-28', status: 'Planned', downloads: 0, },];/* end:skip-in-preview */// Get the DOM element with the ID 'example1' where the Handsontable will be renderedconst container = document.querySelector('#example1');const cellDefinition = { validator: (value, callback) => { callback(isDate(new Date(value))); }, renderer: rendererFactory(({ td, value, cellProperties }) => { td.innerText = value ? format(new Date(value), cellProperties.renderFormat) : ''; }), editor: editorFactory({ init(editor) { editor.input = editor.hot.rootDocument.createElement('INPUT'); editor.input.classList.add('flatpickr-editor');
editor.flatpickr = flatpickr(editor.input, { dateFormat: 'Y-m-d', onClose: () => { editor.finishEditing(); }, });
editor.preventCloseElement = editor.flatpickr.calendarContainer;
/** * Prepare dark theme stylesheet for dynamic loading. */ editor._darkThemeLink = editor.hot.rootDocument.createElement('LINK'); editor._darkThemeLink.rel = 'stylesheet'; editor._darkThemeLink.href = 'https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/dark.css'; }, afterClose(editor) { editor.flatpickr.close(); }, afterOpen(editor) { const isDark = editor.hot.rootDocument.documentElement.getAttribute('data-theme') === 'dark';
const head = editor.hot.rootDocument.head;
if (isDark && !editor._darkThemeLink.parentNode) { head.appendChild(editor._darkThemeLink); } else if (!isDark && editor._darkThemeLink.parentNode) { head.removeChild(editor._darkThemeLink); }
editor.flatpickr.open(); }, beforeOpen(editor, { cellProperties }) { for (const key in cellProperties.flatpickrSettings) { editor.flatpickr.set(key, cellProperties.flatpickrSettings[key]); } }, getValue(editor) { return editor.input.value; }, setValue(editor, value) { editor.input.value = value; editor.flatpickr.setDate(value ? new Date(value) : new Date()); }, }),};
// Define configuration options for the Handsontableconst hotOptions = { data, colHeaders: ['Product', 'Version', 'Release (EU)', 'Release (US)', 'Status'], autoRowSize: true, rowHeaders: true, height: 'auto', width: '100%', autoWrapRow: true, headerClassName: 'htLeft', columns: [ { data: 'product', type: 'text', width: 200 }, { data: 'version', type: 'text', width: 80 }, { data: 'releaseDate', width: 130, allowInvalid: false, ...cellDefinition, renderFormat: DATE_FORMAT_EU, flatpickrSettings: { locale: { firstDayOfWeek: 1, }, }, }, { data: 'releaseDate', width: 130, allowInvalid: false, ...cellDefinition, renderFormat: DATE_FORMAT_US, flatpickrSettings: { locale: { firstDayOfWeek: 0, }, }, }, { data: 'status', type: 'text', width: 130 }, ], licenseKey: 'non-commercial-and-evaluation',};
// Initialize the Handsontable instance with the specified configuration options// eslint-disable-next-line no-unused-varsconst hot = new Handsontable(container, hotOptions);import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { format, isDate } from 'date-fns';import flatpickr from 'flatpickr';import 'flatpickr/dist/flatpickr.css';import { CellProperties } from 'handsontable/settings';import { editorFactory } from 'handsontable/editors';import { rendererFactory } from 'handsontable/renderers';
registerAllModules();const DATE_FORMAT_US = 'MM/dd/yyyy';const DATE_FORMAT_EU = 'dd/MM/yyyy';
/* start:skip-in-preview */const data = [ { product: 'Dashboard Pro', category: 'Analytics', version: '3.2.0', releaseDate: '2025-03-15', status: 'Released', downloads: 12450, }, { product: 'Form Builder', category: 'Tools', version: '2.1.0', releaseDate: '2025-04-22', status: 'Released', downloads: 8320, }, { product: 'Chart Engine', category: 'Analytics', version: '4.0.0', releaseDate: '2025-06-10', status: 'Beta', downloads: 3100, }, { product: 'Auth Module', category: 'Security', version: '1.5.2', releaseDate: '2025-07-01', status: 'Released', downloads: 15600, }, { product: 'File Manager', category: 'Storage', version: '2.0.0', releaseDate: '2025-08-18', status: 'Planned', downloads: 0, }, { product: 'Email Service', category: 'Communication', version: '3.1.0', releaseDate: '2025-09-05', status: 'Released', downloads: 9870, }, { product: 'Search Index', category: 'Tools', version: '1.2.0', releaseDate: '2025-10-12', status: 'Beta', downloads: 2450, }, { product: 'Cache Layer', category: 'Infra', version: '2.3.1', releaseDate: '2025-11-28', status: 'Planned', downloads: 0, },];/* end:skip-in-preview */// Get the DOM element with the ID 'example1' where the Handsontable will be renderedconst container = document.querySelector('#example1')!;
interface FlatpickrEditorInstance { input: HTMLInputElement; flatpickr: flatpickr.Instance; preventCloseElement: HTMLElement; _darkThemeLink: HTMLLinkElement;}
const cellDefinition: Pick< CellProperties, 'renderer' | 'validator' | 'editor'> = { validator: (value, callback) => { callback(isDate(new Date(value))); }, renderer: rendererFactory(({ td, value, cellProperties }) => { td.innerText = value ? format(new Date(value), cellProperties.renderFormat) : ''; }), editor: editorFactory<FlatpickrEditorInstance>({ init(editor) { editor.input = editor.hot.rootDocument.createElement('INPUT') as HTMLInputElement; editor.input.classList.add('flatpickr-editor');
editor.flatpickr = flatpickr(editor.input, { dateFormat: 'Y-m-d', onClose: () => { editor.finishEditing(); }, });
editor.preventCloseElement = editor.flatpickr.calendarContainer;
/** * Prepare dark theme stylesheet for dynamic loading. */ editor._darkThemeLink = editor.hot.rootDocument.createElement('LINK') as HTMLLinkElement; editor._darkThemeLink.rel = 'stylesheet'; editor._darkThemeLink.href = 'https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/dark.css'; }, afterClose(editor) { editor.flatpickr.close(); }, afterOpen(editor) { const isDark = editor.hot.rootDocument.documentElement.getAttribute('data-theme') === 'dark';
const head = editor.hot.rootDocument.head;
if (isDark && !editor._darkThemeLink.parentNode) { head.appendChild(editor._darkThemeLink); } else if (!isDark && editor._darkThemeLink.parentNode) { head.removeChild(editor._darkThemeLink); }
editor.flatpickr.open(); }, beforeOpen(editor, { cellProperties }) { for (const key in cellProperties.flatpickrSettings) { editor.flatpickr.set(key as keyof flatpickr.Options.Options, cellProperties.flatpickrSettings[key]); } }, getValue(editor) { return editor.input.value; }, setValue(editor, value) { editor.input.value = value; editor.flatpickr.setDate(value ? new Date(value) : new Date()); }, }),};
// Define configuration options for the Handsontableconst hotOptions: Handsontable.GridSettings = { data, colHeaders: ['Product', 'Version', 'Release (EU)', 'Release (US)', 'Status'], autoRowSize: true, rowHeaders: true, height: 'auto', width: '100%', autoWrapRow: true, headerClassName: 'htLeft', columns: [ { data: 'product', type: 'text', width: 200 }, { data: 'version', type: 'text', width: 80 }, { data: 'releaseDate', width: 130, allowInvalid: false, ...cellDefinition, renderFormat: DATE_FORMAT_EU, flatpickrSettings: { locale: { firstDayOfWeek: 1, }, }, }, { data: 'releaseDate', width: 130, allowInvalid: false, ...cellDefinition, renderFormat: DATE_FORMAT_US, flatpickrSettings: { locale: { firstDayOfWeek: 0, }, }, }, { data: 'status', type: 'text', width: 130 }, ], licenseKey: 'non-commercial-and-evaluation',};
// Initialize the Handsontable instance with the specified configuration options// eslint-disable-next-line no-unused-varsconst hot = new Handsontable(container, hotOptions);.flatpickr-editor { width: 100%; height: 100%; box-sizing: border-box !important; border: none; border-radius: 0; outline: none; box-shadow: inset 0 0 0 var(--ht-cell-editor-border-width, 2px) var(--ht-cell-editor-border-color, #1a42e8), 0 0 var(--ht-cell-editor-shadow-blur-radius, 0) 0 var(--ht-cell-editor-shadow-color, transparent) !important; background-color: var(--ht-cell-editor-background-color, #ffffff) !important; padding: var(--ht-cell-vertical-padding, 4px) var(--ht-cell-horizontal-padding, 8px) !important; border: none !important; font-family: inherit; font-size: inherit; line-height: inherit;}.flatpickr-editor:focus-visible { border: none !important;}
.flatpickr-editor.active { box-shadow: none; background-color: transparent; border-radius: 0 !important; border-radius: none;}Overview
This guide shows how to create a custom date picker cell using Flatpickr, a powerful and flexible date picker library. This is more advanced than using native HTML5 date inputs, offering better cross-browser consistency and extensive customization options.
Difficulty: Intermediate
Time: ~20 minutes
Libraries: flatpickr, date-fns
What You’ll Build
A cell that:
- Displays formatted dates (e.g., “12/31/2024” or “31/12/2024”)
- Opens a beautiful calendar picker when edited
- Supports per-column configuration (EU vs US date formats)
- Handles locale-specific settings (first day of week)
- Auto-closes and saves when a date is selected
- Supports dark theme with dynamic stylesheet loading
Prerequisites
npm install flatpickr date-fnsImport Dependencies
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';import { format, isDate } from 'date-fns';import flatpickr from 'flatpickr';import 'flatpickr/dist/flatpickr.css';import { editorFactory } from 'handsontable/editors';import { rendererFactory } from 'handsontable/renderers';registerAllModules();Why date-fns?
- Lightweight, modular date formatting
- Better than native
toLocaleDateString()for consistency - Can be replaced with other libraries (moment, dayjs, etc.)
- We import
isDatefor validation
Why
editorFactoryandrendererFactory?editorFactorycreates a custom editor with lifecycle hooks (init,beforeOpen,afterOpen,afterClose,getValue,setValue, etc.)rendererFactorycreates a custom renderer with access to cell properties- Both are Handsontable helpers that handle boilerplate like container creation, positioning, and lifecycle management
Define Date Formats
const DATE_FORMAT_US = 'MM/dd/yyyy';const DATE_FORMAT_EU = 'dd/MM/yyyy';Why constants?
- Reusability across renderer and column configuration
- Single source of truth
- Easy to add more formats (ISO, custom, etc.)
Create the Renderer
The renderer displays the date in a human-readable format.
renderer: rendererFactory(({ td, value, cellProperties }) => {td.innerText = value ? format(new Date(value), cellProperties.renderFormat) : '';})What’s happening:
valueis the raw date value (e.g., ISO string “2025-03-15”)- Empty or invalid values are shown as an empty string
cellProperties.renderFormatis a custom property we’ll set per columnformat()from date-fns converts to desired format- Display the formatted date
Why use
cellProperties?- Allows different columns to display dates differently
- One cell definition, multiple configurations
Optional error handling for production:
renderer: rendererFactory(({ td, value, cellProperties }) => {if (!value) {td.innerText = '';return;}try {td.innerText = format(new Date(value), cellProperties.renderFormat || 'MM/dd/yyyy');} catch (e) {td.innerText = 'Invalid date';td.style.color = 'red';}})Create the Validator
validator: (value, callback) => {callback(isDate(new Date(value)));}What’s happening:
- Uses
isDatefrom date-fns to validate the date isDatechecks if the value is a valid Date object- Returns
truefor valid dates,falsefor invalid ones
Alternative validation approaches:
// Using native JavaScriptvalidator: (value, callback) => {const date = new Date(value);callback(!isNaN(date.getTime()));}- Uses
Editor - Initialize (
init)Create the input element, initialize Flatpickr, and prepare the dark theme.
init(editor) {editor.input = editor.hot.rootDocument.createElement('INPUT') as HTMLInputElement;editor.input.classList.add('flatpickr-editor');editor.flatpickr = flatpickr(editor.input, {dateFormat: 'Y-m-d',onClose: () => {editor.finishEditing();},});editor.preventCloseElement = editor.flatpickr.calendarContainer;/*** Prepare dark theme stylesheet for dynamic loading.*/editor._darkThemeLink = editor.hot.rootDocument.createElement('LINK') as HTMLLinkElement;editor._darkThemeLink.rel = 'stylesheet';editor._darkThemeLink.href = 'https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/dark.css';}What’s happening:
- Create an
inputelement and add theflatpickr-editorCSS class for styling - Initialize Flatpickr on the input with an
onClosehandler that callsfinishEditing()when the calendar closes (e.g., after selecting a date or pressing Escape) - Set
preventCloseElementto the Flatpickr calendar container so that clicks inside the calendar are not treated as “outside” clicks — this keeps the editor open while the user selects a date - Create a
<link>element for the dark theme (loaded dynamically inafterOpen)
Key concepts:
The
onClosehandleronClose: () => {editor.finishEditing();},When the user selects a date or closes the calendar (e.g., Escape), Flatpickr fires
onClose. CallingfinishEditing()saves the value and closes the editor.The CSS class
editor.input.classList.add('flatpickr-editor');Adds a CSS class to style the input element using Handsontable’s CSS custom properties (tokens). See the CSS file for details.
The
preventCloseElementpatternWithout it:
- User clicks cell to edit
- Flatpickr calendar opens
- User clicks on the calendar
- Handsontable treats the click as “outside” the editor and closes it
Solution: Assign the Flatpickr calendar container to
editor.preventCloseElement. The editor factory treats this element as part of the editor, so clicks inside it do not close the editor prematurely.- Create an
Editor - After Close Hook (
afterClose)Close the Flatpickr calendar when the editor is closed by non-Flatpickr means (e.g., Escape key or clicking outside).
afterClose(editor) {editor.flatpickr.close();}What’s happening:
- When the user closes the editor by pressing Escape or clicking outside the cell, Handsontable closes the editor but Flatpickr’s calendar popup is not automatically closed
- The
afterClosehook runs when the editor is about to close; callingeditor.flatpickr.close()ensures the calendar is hidden - Without this hook, the calendar would remain visible on screen after the editor has closed
Editor - After Open Hook (
afterOpen)Toggle the dark theme when the editor opens, then open the Flatpickr calendar.
afterOpen(editor) {const isDark = editor.hot.rootDocument.documentElement.getAttribute('data-theme') === 'dark';const head = editor.hot.rootDocument.head;if (isDark && !editor._darkThemeLink.parentNode) {head.appendChild(editor._darkThemeLink);} else if (!isDark && editor._darkThemeLink.parentNode) {head.removeChild(editor._darkThemeLink);}editor.flatpickr.open();}What’s happening:
- Check the current theme by reading the
data-themeattribute on the<html>element - Dynamically add or remove the Flatpickr dark theme stylesheet
- Call
editor.flatpickr.open()to show the calendar (the editor factory does not open Flatpickr automatically)
Why dynamic theme loading?
- Flatpickr themes are CSS files, not runtime APIs
- Importing CSS directly (
import 'flatpickr/dist/themes/dark.css') doesn’t work in all build environments - Dynamic
<link>injection allows toggling the theme each time the editor opens - The theme stylesheet is loaded from jsDelivr CDN and cached by the browser
- Check the current theme by reading the
Editor - Before Open Hook (
beforeOpen)Apply per-column Flatpickr settings.
beforeOpen(editor, { cellProperties }) {for (const key in cellProperties.flatpickrSettings) {editor.flatpickr.set(key as keyof flatpickr.Options.Options, cellProperties.flatpickrSettings[key]);}}What’s happening:
- Update Flatpickr settings from
cellProperties.flatpickrSettings(e.g., locale, first day of week) - The editor’s value is set by the framework before
beforeOpenruns
Key points:
beforeOpenis called before the editor openscellProperties.flatpickrSettingscontains column-specific Flatpickr configurationflatpickr.set()updates the existing Flatpickr instance with new settings
Why update settings in
beforeOpen?- Allows different columns to have different Flatpickr configurations (e.g., EU vs US first day of week)
- Settings are applied just before opening, ensuring they’re fresh
- More efficient than reinitializing Flatpickr each time
- Update Flatpickr settings from
Editor - Get Value (
getValue)Return the current date value from the input.
getValue(editor) {return editor.input.value;}What’s happening:
- Flatpickr automatically updates
input.valuein ISO format (e.g., “2025-03-15”) - Simply return the input’s current value
- Called when Handsontable needs to save the cell value
- Flatpickr automatically updates
Editor - Set Value (
setValue)Initialize the editor with the cell’s current date value.
setValue(editor, value) {editor.input.value = value;editor.flatpickr.setDate(value ? new Date(value) : new Date());}What’s happening:
- Set the input’s value to the provided date string
- Update Flatpickr’s selected date using
setDate()— use the cell value if present, otherwise the current date - This ensures Flatpickr displays the correct date when opened
- Called to initialize the editor with the cell’s current value
Style the Editor with CSS
The editor input needs styling to match Handsontable’s cell appearance. Create a CSS file using Handsontable’s CSS custom properties (tokens):
.flatpickr-editor {width: 100%;height: 100%;box-sizing: border-box !important;border: none;border-radius: 0;outline: none;box-shadow: inset 0 0 0 var(--ht-cell-editor-border-width, 2px)var(--ht-cell-editor-border-color, #1a42e8),0 0 var(--ht-cell-editor-shadow-blur-radius, 0) 0var(--ht-cell-editor-shadow-color, transparent) !important;background-color: var(--ht-cell-editor-background-color, #ffffff) !important;padding: var(--ht-cell-vertical-padding, 4px)var(--ht-cell-horizontal-padding, 8px) !important;border: none !important;font-family: inherit;font-size: inherit;line-height: inherit;}.flatpickr-editor:focus-visible {border: none !important;}.flatpickr-editor.active {box-shadow: none;background-color: transparent;border-radius: 0 !important;}Why use CSS custom properties?
--ht-cell-editor-border-widthand--ht-cell-editor-border-colormatch the default Handsontable editor border--ht-cell-editor-background-colorensures the editor background matches the cell--ht-cell-vertical-paddingand--ht-cell-horizontal-paddingalign text with the cell content- These tokens automatically adapt when using custom Handsontable themes
The
.activestate: When the editor is active (calendar open), the input border and background are hidden since the calendar itself provides the visual feedback.Complete Cell Definition
TypeScript: Define an interface for the editor instance (optional in JavaScript):
interface FlatpickrEditorInstance {input: HTMLInputElement;flatpickr: flatpickr.Instance;preventCloseElement: HTMLElement;_darkThemeLink: HTMLLinkElement;}Put it all together:
const cellDefinition = {validator: (value, callback) => {callback(isDate(new Date(value)));},renderer: rendererFactory(({ td, value, cellProperties }) => {td.innerText = value ? format(new Date(value), cellProperties.renderFormat) : '';}),editor: editorFactory<FlatpickrEditorInstance>({init(editor) {editor.input = editor.hot.rootDocument.createElement('INPUT') as HTMLInputElement;editor.input.classList.add('flatpickr-editor');editor.flatpickr = flatpickr(editor.input, {dateFormat: 'Y-m-d',onClose: () => {editor.finishEditing();},});editor.preventCloseElement = editor.flatpickr.calendarContainer;editor._darkThemeLink = editor.hot.rootDocument.createElement('LINK') as HTMLLinkElement;editor._darkThemeLink.rel = 'stylesheet';editor._darkThemeLink.href = 'https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/dark.css';},afterClose(editor) {editor.flatpickr.close();},afterOpen(editor) {const isDark = editor.hot.rootDocument.documentElement.getAttribute('data-theme') === 'dark';const head = editor.hot.rootDocument.head;if (isDark && !editor._darkThemeLink.parentNode) {head.appendChild(editor._darkThemeLink);} else if (!isDark && editor._darkThemeLink.parentNode) {head.removeChild(editor._darkThemeLink);}editor.flatpickr.open();},beforeOpen(editor, { cellProperties }) {for (const key in cellProperties.flatpickrSettings) {editor.flatpickr.set(key as keyof flatpickr.Options.Options, cellProperties.flatpickrSettings[key]);}},getValue(editor) {return editor.input.value;},setValue(editor, value) {editor.input.value = value;editor.flatpickr.setDate(value ? new Date(value) : new Date());},}),};What’s happening:
- validator: Ensures date is valid using
isDatefrom date-fns - renderer: Displays formatted date using
cellProperties.renderFormat(empty string for missing values) - editor: Uses
editorFactoryhelper with:init: Creates input, initializes Flatpickr withonClosecallingfinishEditing(), setspreventCloseElementso calendar clicks don’t close the editor, prepares dark theme linkafterClose: Closes the Flatpickr calendar when the editor is closed by Escape or clicking outsideafterOpen: Toggles dark theme stylesheet and opens the Flatpickr calendarbeforeOpen: Applies per-column Flatpickr settings fromcellProperties.flatpickrSettingsgetValue: Returns the input’s current valuesetValue: Sets input value and Flatpickr’s selected date
Note: The
editorFactoryhelper handles container creation, positioning, and lifecycle management automatically.- validator: Ensures date is valid using
Use in Handsontable with Different Formats
const container = document.querySelector('#example1');const hotOptions = {data,colHeaders: ['Product', 'Version', 'Release (EU)', 'Release (US)', 'Status'],autoRowSize: true,rowHeaders: true,height: 'auto',width: '100%',autoWrapRow: true,headerClassName: 'htLeft',columns: [{ data: 'product', type: 'text', width: 200 },{ data: 'version', type: 'text', width: 80 },{data: 'releaseDate',width: 130,allowInvalid: false,...cellDefinition,renderFormat: DATE_FORMAT_EU,flatpickrSettings: {locale: {firstDayOfWeek: 1,},},},{data: 'releaseDate',width: 130,allowInvalid: false,...cellDefinition,renderFormat: DATE_FORMAT_US,flatpickrSettings: {locale: {firstDayOfWeek: 0,},},},{ data: 'status', type: 'text', width: 130 },],licenseKey: 'non-commercial-and-evaluation',};const hot = new Handsontable(container, hotOptions);TypeScript: Use
const container = document.querySelector('#example1')!and type the options asHandsontable.GridSettings.Key feature:
- Same data column (
releaseDate) - Two different display formats (EU vs US)
- Two different calendar configurations (Monday vs Sunday first day)
- One cell definition!
- Same data column (
How It Works - Complete Flow
- Initial Load: Cell displays formatted date (“15/03/2025” EU or “03/15/2025” US), or empty string when there is no value
- User Double-Clicks or F2: Editor opens, container positioned over cell
- Before Open:
beforeOpenapplies per-column Flatpickr settings (e.g., first day of week) - After Open:
afterOpentoggles dark theme if needed and callseditor.flatpickr.open()to show the calendar - Calendar Opens: Flatpickr displays the calendar with column-specific settings
- User Selects Date or Closes Calendar:
onClosehandler fires, callsfinishEditing() - Validation: Validator checks date is valid using
isDate - Save: Value saved in ISO format (“2025-03-15”)
- Editor Closes:
afterCloseruns and closes the Flatpickr calendar (important when the user closed via Escape or clicking outside). Container hidden, cell renderer displays the new formatted date
Advanced Enhancements
Time Picker
Add time selection:
flatpickrSettings: {enableTime: true,dateFormat: 'Y-m-d H:i',time_24hr: true}// Update rendererrenderFormat: 'dd/MM/yyyy HH:mm'Date Range Restrictions
Limit selectable dates:
flatpickrSettings: {minDate: '2024-01-01',maxDate: '2024-12-31',disable: [// Disable weekendsfunction(date) {return (date.getDay() === 0 || date.getDay() === 6);}]}Inline Calendar
Show calendar always visible:
flatpickrSettings: {inline: true,static: true}// Adjust wrapper height in open()editor.container.style.height = '300px';Multiple Date Locales
Use Flatpickr locales:
import { French } from 'flatpickr/dist/l10n/fr.js';flatpickrSettings: {locale: French,dateFormat: 'd/m/Y'}Custom Date Ranges
Add shortcuts:
// Requires flatpickr shortcutButtonsPluginimport ShortcutButtonsPlugin from 'flatpickr/dist/plugins/shortcutButtons/shortcutButtons.js';flatpickrSettings: {plugins: [ShortcutButtonsPlugin({button: [{ label: 'Today' },{ label: 'Next Week' },],onClick: (index, fp) => {let date;if (index === 0) {date = new Date();} else if (index === 1) {date = new Date();date.setDate(date.getDate() + 7);}fp.setDate(date);}})]}
Congratulations! You’ve created a production-ready date picker with full localization support, dark theme toggling, and advanced configuration.