Time cell type
Display, format, sort, and filter time values correctly by using the time cell type. Edit times via the cell editor.
The time cell type formats time values using a configurable format string. Use it for scheduling, logging, or any time-based data.
Overview
The time cell type lets you treat cell values as times: format how they are displayed and validate input. Use the intl-time or time cell type with the native Intl.DateTimeFormat API and 24-hour time strings.
Time cell type demo
In the following demo, the Start, Break start, and End columns use the time cell type with different formats: short style, custom format with hours, minutes, and seconds, and format with day period. Use the locale selector to see how each format varies by locale.
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const container = document.querySelector('#example1');const dropdown = document.querySelector('#localeDropdown');const trigger = document.querySelector('#localeTrigger');const menu = document.querySelector('#localeMenu');const label = document.querySelector('#localeLabel');7 collapsed lines
const data = [ { shift: 'Morning', start: '09:00', breakStart: '12:00', end: '17:00' }, { shift: 'Afternoon', start: '13:30', breakStart: '16:00', end: '21:00' }, { shift: 'Night', start: '22:00', breakStart: '01:00', end: '06:00' }, { shift: 'Split', start: '08:00', breakStart: '12:30', end: '20:00' }, { shift: 'Short day', start: '10:00', breakStart: '13:00', end: '15:00' },];
const hot = new Handsontable(container, { data, colHeaders: ['Shift', 'Start', 'Break start', 'End'],31 collapsed lines
columns: [ { type: 'text', data: 'shift', }, { type: 'intl-time', data: 'start', timeFormat: { timeStyle: 'short', }, }, { type: 'intl-time', data: 'breakStart', timeFormat: { hour: '2-digit', minute: '2-digit', second: '2-digit', }, }, { type: 'intl-time', data: 'end', timeFormat: { hour: 'numeric', hourCycle: 'h12', dayPeriod: 'short', }, }, ], columnSorting: true, filters: true, dropdownMenu: true, height: 'auto', licenseKey: 'non-commercial-and-evaluation', autoWrapRow: true, autoWrapCol: true,});
// Handle dropdown toggletrigger.addEventListener('click', () => { const isOpen = !menu.hidden;
menu.hidden = isOpen; trigger.setAttribute('aria-expanded', String(!isOpen));});
// Handle locale selectionmenu.addEventListener('click', (e) => { const item = e.target.closest('li[data-value]');
if (item) { label.textContent = item.textContent.trim(); menu.querySelectorAll('li').forEach((li) => li.setAttribute('aria-selected', 'false')); item.setAttribute('aria-selected', 'true'); menu.hidden = true; trigger.setAttribute('aria-expanded', 'false'); hot.updateSettings({ locale: item.dataset.value }); }});
// Close dropdown when clicking outsidedocument.addEventListener('click', (e) => { if (!dropdown.contains(e.target)) { menu.hidden = true; trigger.setAttribute('aria-expanded', 'false'); }});
// Close dropdown on Escape keydocument.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !menu.hidden) { menu.hidden = true; trigger.setAttribute('aria-expanded', 'false'); trigger.focus(); }});import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.registerAllModules();
const container = document.querySelector('#example1')!;const dropdown = document.querySelector('#localeDropdown')!;const trigger = document.querySelector('#localeTrigger')!;const menu = document.querySelector('#localeMenu')!;const label = document.querySelector('#localeLabel')!;7 collapsed lines
const data = [ { shift: 'Morning', start: '09:00', breakStart: '12:00', end: '17:00' }, { shift: 'Afternoon', start: '13:30', breakStart: '16:00', end: '21:00' }, { shift: 'Night', start: '22:00', breakStart: '01:00', end: '06:00' }, { shift: 'Split', start: '08:00', breakStart: '12:30', end: '20:00' }, { shift: 'Short day', start: '10:00', breakStart: '13:00', end: '15:00' },];
const hot = new Handsontable(container, { data, colHeaders: ['Shift', 'Start', 'Break start', 'End'],31 collapsed lines
columns: [ { type: 'text', data: 'shift', }, { type: 'intl-time', data: 'start', timeFormat: { timeStyle: 'short', }, }, { type: 'intl-time', data: 'breakStart', timeFormat: { hour: '2-digit', minute: '2-digit', second: '2-digit', }, }, { type: 'intl-time', data: 'end', timeFormat: { hour: 'numeric', hourCycle: 'h12', dayPeriod: 'short', }, }, ], columnSorting: true, filters: true, dropdownMenu: true, height: 'auto', licenseKey: 'non-commercial-and-evaluation', autoWrapRow: true, autoWrapCol: true,});
// Handle dropdown toggletrigger.addEventListener('click', () => { const isOpen = !menu.hidden;
menu.hidden = isOpen; trigger.setAttribute('aria-expanded', String(!isOpen));});
// Handle locale selectionmenu.addEventListener('click', (e) => { const item = (e.target as HTMLElement).closest('li[data-value]') as HTMLLIElement | null;
if (item) { label.textContent = item.textContent!.trim(); menu.querySelectorAll('li').forEach((li) => li.setAttribute('aria-selected', 'false')); item.setAttribute('aria-selected', 'true'); menu.hidden = true; trigger.setAttribute('aria-expanded', 'false'); hot.updateSettings({ locale: item.dataset.value }); }});
// Close dropdown when clicking outsidedocument.addEventListener('click', (e) => { if (!dropdown.contains(e.target as Node)) { menu.hidden = true; trigger.setAttribute('aria-expanded', 'false'); }});
// Close dropdown on Escape keydocument.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !menu.hidden) { menu.hidden = true; trigger.setAttribute('aria-expanded', 'false'); trigger.focus(); }});<div class="example-controls-container"> <div class="controls"> <div class="theme-dropdown" id="localeDropdown"> <button class="theme-dropdown-trigger" type="button" id="localeTrigger" aria-haspopup="listbox" aria-expanded="false" > <span id="localeLabel">English (United States)</span> <svg class="theme-dropdown-chevron" aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6l6 -6"/></svg> </button> <ul class="theme-dropdown-menu" role="listbox" id="localeMenu" hidden> <li role="option" data-value="ar-AR">Arabic (Global)</li> <li role="option" data-value="cs-CZ">Czech (Czechia)</li> <li role="option" data-value="de-CH">German (Switzerland)</li> <li role="option" data-value="de-DE">German (Germany)</li> <li role="option" data-value="en-US" aria-selected="true">English (United States)</li> <li role="option" data-value="es-MX">Spanish (Mexico)</li> <li role="option" data-value="fa-IR">Persian (Iran)</li> <li role="option" data-value="fr-FR">French (France)</li> <li role="option" data-value="hr-HR">Croatian (Croatia)</li> <li role="option" data-value="it-IT">Italian (Italy)</li> <li role="option" data-value="ja-JP">Japanese (Japan)</li> <li role="option" data-value="ko-KR">Korean (Korea)</li> <li role="option" data-value="lv-LV">Latvian (Latvia)</li> <li role="option" data-value="nb-NO">Norwegian Bokmal (Norway)</li> <li role="option" data-value="nl-NL">Dutch (Netherlands)</li> <li role="option" data-value="pl-PL">Polish (Poland)</li> <li role="option" data-value="pt-BR">Portuguese (Brazil)</li> <li role="option" data-value="ru-RU">Russian (Russia)</li> <li role="option" data-value="sr-SP">Serbian Latin (Serbia)</li> <li role="option" data-value="zh-CN">Chinese (Simplified, China)</li> <li role="option" data-value="zh-TW">Chinese (Traditional, Taiwan)</li> </ul> </div> </div></div>
<div id="example1"></div>Use the time cell type
Use the object-style configuration by setting the type option to 'intl-time' or 'time' and timeFormat to an object. The locale is controlled via the locale option.
// set the time cell type for the entire gridtype: 'intl-time',locale: 'en-US',timeFormat: { hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true},
// set the time cell type for a single columncolumns: [ { type: 'intl-time', locale: 'en-US', timeFormat: { timeStyle: 'medium' } }],
// set the time cell type for a single cellcell: [ { row: 0, col: 2, type: 'intl-time', locale: 'en-US', timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true } }],For intl-time and time cells, source data must be in 24-hour time format (HH:mm, HH:mm:ss, or HH:mm:ss.SSS) for times to work correctly. The timeFormat object only affects how times are displayed; sorting and filtering rely on the underlying value.
Format times
To control how times are displayed in cell renderers, use the timeFormat option.
Since Handsontable 18.0, the object form of timeFormat with the intl-time and time cell types is required. It uses the native Intl.DateTimeFormat API. The locale is controlled separately via the locale option.
Using Intl.DateTimeFormat
The timeFormat option accepts properties of Intl.DateTimeFormat options relevant to time. Use it with type: 'intl-time' or type: 'time'.
columns: [ { type: 'intl-time', locale: 'en-US', timeFormat: { hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true } }, { type: 'intl-time', locale: 'de-DE', timeFormat: { timeStyle: 'medium' } }]Time-specific options
Style shortcuts:
| Property | Possible values | Description |
|---|---|---|
timeStyle | 'full', 'long', 'medium', 'short' | Time formatting style (hour, minute, second, timeZoneName) |
Time component options:
| Property | Possible values | Description |
|---|---|---|
hour | 'numeric', '2-digit' | Hour representation |
minute | 'numeric', '2-digit' | Minute representation |
second | 'numeric', '2-digit' | Second representation |
fractionalSecondDigits | 1, 2, 3 | Fraction-of-second digits |
dayPeriod | 'narrow', 'short', 'long' | Day period (e.g. “am”) |
timeZoneName | 'long', 'short', 'shortOffset', 'longOffset', 'shortGeneric', 'longGeneric' | Time zone display |
Locale and other options:
| Property | Possible values | Description |
|---|---|---|
localeMatcher | 'best fit' (default), 'lookup' | Locale matching algorithm |
timeZone | IANA time zone (e.g. 'UTC', 'America/New_York') | Time zone for formatting |
hour12 | true, false | 12-hour vs 24-hour time |
hourCycle | 'h11', 'h12', 'h23', 'h24' | Hour cycle |
formatMatcher | 'basic', 'best fit' (default) | Format matching algorithm |
For a complete reference, see the timeFormat API documentation or MDN: Intl.DateTimeFormat.
Editor behavior
The timeFormat option controls how times are displayed in the cell. The editor may show the value in a normalized form; for intl-time and time, the underlying value remains in 24-hour format (HH:mm, HH:mm:ss, or HH:mm:ss.SSS).
Result
After configuring the time cell type, cells display time values formatted according to your timeFormat configuration. Clicking an intl-time or time cell opens a native time picker. Source data is stored in 24-hour format (HH:mm, HH:mm:ss, or HH:mm:ss.SSS) regardless of the display format.
Related articles
Related guides
Configuration options
Core methods
Hooks