DateTimeInput
Overview
The DateTimeInput combines a calendar and a time wheel spinner in a single popover — an iOS-style layout.
The popover opens to the calendar with an iOS-style footer: the selected time as a tappable value, a Reset button (reverts your edits to the value the popover opened with), and a circular ✓ confirm button. Clicking the time floats the wheel spinner over the calendar (hours, minutes, and optionally seconds — the same wheels the TimeInput uses), so the popover never grows or scrolls; clicking the time again, clicking outside the wheel card, or pressing Escape hides it. It supports the full prop surface of both DateInput and TimeInput, plus a native <input type="datetime-local"> fallback for touch devices. A clickable launcher icon on the right opens the popover, and you can type directly in the field with segmented keyboard entry.
Import
import { DateTimeInput } from '@allxsmith/bestax-bulma';
Usage
Basic DateTimeInput
The popover opens to the calendar with an iOS-style footer — the selected time, a Reset button, and a circular ✓ to confirm. Click the time value to float the wheel spinner over the calendar.
<DateTimeInput label="Appointment" placeholder="YYYY-MM-DD HH:MM" />
Typing-first — the same example with openOnFocus={false}: focusing or clicking the field lets you type; open the popover with the launcher icon (or press Alt+↓).
<DateTimeInput label="Appointment" placeholder="YYYY-MM-DD HH:MM" openOnFocus={false} />
Controlled
function example() { const [v, setV] = useState(new Date()); return ( <Block> <DateTimeInput label="Meeting" value={v} onChange={setV} /> <Paragraph mt="2">Selected: {v ? v.toString() : '—'}</Paragraph> </Block> ); }
Typing-first — the same controlled example with openOnFocus={false} added: click in and type freely, and reach for the launcher icon (or Alt+↓) when you want the popover.
function example() { const [v, setV] = useState(new Date()); return ( <Block> <DateTimeInput label="Meeting" value={v} onChange={setV} openOnFocus={false} /> <Paragraph mt="2">Selected: {v ? v.toString() : '—'}</Paragraph> </Block> ); }
12-hour Format
<DateTimeInput label="12-hour" hourFormat="12" defaultValue={new Date()} mobileNative={false} />
The OS-native pickers use the device clock setting, so hourFormat is ignored there. This example forces mobileNative={false} so the 12-hour format shows on touch devices too.
Typing-first — the same 12-hour field with openOnFocus={false}: type across the segments (press a / p on the trailing meridiem) and open the popover with the launcher icon or Alt+↓.
function example() { return ( <DateTimeInput label="12-hour entry" hourFormat="12" defaultValue={new Date(2024, 5, 7, 13, 45)} mobileNative={false} openOnFocus={false} /> ); }
With Seconds
<DateTimeInput label="With seconds" enableSeconds defaultValue={new Date()} mobileNative={false} />
This example forces mobileNative={false} so the seconds wheel shows on every device.
Android Chrome generally renders a seconds component in its native datetime-local picker when step < 60 (with some long-standing quirks — exact seconds-spinner behavior varies by Android version). iOS Safari has no seconds wheel under any circumstances. If you need a guaranteed seconds wheel, pass mobileNative={false} to force the custom wheel popover. See Mobile Native below for the full iOS-vs-Android picker support matrix.
Typing-first — the same seconds-enabled field with openOnFocus={false}: the typed walk gains a seconds segment (year → month → day → hours → minutes → seconds), and the launcher icon (or Alt+↓) opens the popover.
function example() { return ( <DateTimeInput label="With a seconds segment" enableSeconds defaultValue={new Date(2024, 5, 7, 13, 45, 30)} mobileNative={false} openOnFocus={false} /> ); }
12-hour with Seconds
Combine hourFormat="12" with enableSeconds for an hh:mm:ss A field — the wheel gains hours, minutes, seconds, and AM/PM columns.
<DateTimeInput label="12-hour with seconds" hourFormat="12" enableSeconds defaultValue={new Date()} mobileNative={false} />
The OS-native pickers honor neither half: hourFormat follows the device clock setting, and iOS Safari has no seconds wheel at all. This example forces mobileNative={false} so the 12-hour seconds wheel shows on every device.
Typing-first — the full segment set with openOnFocus={false}: type through date, hh, mm, ss, toggle the trailing meridiem with a / p, and open the popover via the launcher icon or Alt+↓.
function example() { return ( <DateTimeInput label="12-hour with seconds and AM/PM" hourFormat="12" enableSeconds defaultValue={new Date(2024, 5, 7, 13, 45, 30)} mobileNative={false} openOnFocus={false} /> ); }
Formats
The format prop takes a token string or Intl.DateTimeFormatOptions spanning the whole date-time. Padded token formats keep the field segmented-typeable; Intl formats are display-only unless you add a custom parse.
<Block display="flex" flexDirection="column"> <DateTimeInput label="YYYY-MM-DD HH:mm (default)" defaultValue={new Date(2026, 4, 30, 13, 45)} mobileNative={false} /> <DateTimeInput label="MM/DD/YYYY hh:mm A" format="MM/DD/YYYY hh:mm A" defaultValue={new Date(2026, 4, 30, 13, 45)} mobileNative={false} /> <DateTimeInput label="DD.MM.YYYY HH:mm" format="DD.MM.YYYY HH:mm" defaultValue={new Date(2026, 4, 30, 13, 45)} mobileNative={false} /> <DateTimeInput label="Intl — display only" format={{ dateStyle: 'medium', timeStyle: 'short' }} editable={false} defaultValue={new Date(2026, 4, 30, 13, 45)} mobileNative={false} /> </Block>
format is ignored by the OS-native pickers (they use the device locale), so these examples set mobileNative={false} to show the formats on touch devices too.
Typing-first — a custom DD.MM.YYYY HH:mm format with openOnFocus={false}: segments follow the format order (day first) and typing ., space, or : jumps the separators — so 25.12.2026 09:30 flows straight through — with the launcher icon (or Alt+↓) opening the popover.
function example() { return ( <DateTimeInput label="DD.MM.YYYY HH:mm with dot separators" format="DD.MM.YYYY HH:mm" defaultValue={new Date(2024, 5, 7, 13, 45)} mobileNative={false} openOnFocus={false} /> ); }
Launcher Icon
A clickable launcher sits on the right and toggles the popover — handy for input-mode (openOnFocus={false}) where you type the value and click the icon to open the picker. Override its glyph with triggerIconName, or hide it with triggerIcon={false} (the popover still opens on focus / click). The decorative left icon is independent: it shows by default, takes its glyph from iconLeftName, and is hidden with iconLeftName="".
<Block display="flex" flexDirection="column"> <DateTimeInput label="Default (left icon + right launcher)" /> <DateTimeInput label="Custom launcher glyph" triggerIconName="calendar-check" /> <DateTimeInput label="No launcher" triggerIcon={false} /> <DateTimeInput label="Left icon hidden" iconLeftName="" /> </Block>
Typing-first — the same group with openOnFocus={false} on every instance, so clicking a field just lets you type and the launcher icon opens the popover; note the triggerIcon={false} instance has no launcher, leaving its popover keyboard-only via Alt+↓.
<Block display="flex" flexDirection="column"> <DateTimeInput label="Default (left icon + right launcher)" openOnFocus={false} /> <DateTimeInput label="Custom launcher glyph" triggerIconName="calendar-check" openOnFocus={false} /> <DateTimeInput label="No launcher (popover via Alt+↓ only)" triggerIcon={false} openOnFocus={false} /> <DateTimeInput label="Left icon hidden" iconLeftName="" openOnFocus={false} /> </Block>
The launcher gives way to a loading spinner at the same right edge, whether the DateTimeInput's own isLoading draws it or a Control it sits in does. Inside your own Control, set isLoading on that Control: the DateTimeInput renders no Control of its own there, so its isLoading draws nothing and warns in development.
<Field label="When"> <Control iconLeftName="calendar-alt" isLoading> <DateTimeInput placeholder="YYYY-MM-DD HH:MM" /> </Control> </Field>
Min and Max
Bounds apply to the combined date-time. Nothing before year 1 is in range, as HTML's datetime-local input holds no earlier year: without a min the year list stops there, and a min before it counts as midnight on 1 January of year 1.
function example() { const today = new Date(); const min = new Date(today); min.setHours(9, 0, 0, 0); const max = new Date(today); max.setHours(17, 0, 0, 0); return <DateTimeInput label="Office hours today" min={min} max={max} />; }
min/max in the pickerOn iOS Safari the picker UI lets the user pick any value; min/max only fire at form-submission validation (WebKit bug #225639, still open). Pass mobileNative={false} for iOS-side enforcement. Android Chrome's native picker does honor them.
Typing-first — the same bounds with openOnFocus={false}: keystrokes and ↑ / ↓ arrows never produce a value outside min/max, and the launcher icon (or Alt+↓) opens the popover.
function example() { const now = new Date(); const min = new Date(now); min.setHours(9, 0, 0, 0); const max = new Date(now); max.setHours(17, 0, 0, 0); const noon = new Date(now); noon.setHours(12, 0, 0, 0); return ( <DateTimeInput label="Office hours today only — typed entry too" min={min} max={max} defaultValue={noon} openOnFocus={false} /> ); }
Disabled Dates
Blocked dates are disabled in the calendar and rejected during manual typing, the same way min/max are enforced.
<DateTimeInput label="No weekend appointments" shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6} mobileNative={false} />
HTML has no predicate equivalent, so the OS-native pickers can't block any dates. This example forces mobileNative={false} so the rule works on touch devices; in your app keep mobileNative="auto" and also validate in onChange.
Typing-first — the same predicate with openOnFocus={false}: a keystroke or arrow that lands on a blocked date is rejected (matching the disabled calendar cells), and the launcher icon (or Alt+↓) opens the popover.
function example() { return ( <DateTimeInput label="Weekends rejected while typing" shouldDisableDate={d => d.getDay() === 0 || d.getDay() === 6} defaultValue={new Date(2024, 5, 7, 13, 45)} mobileNative={false} openOnFocus={false} /> ); }
Unselectable Times
Blocked times are skipped by the wheels and rejected during manual typing.
<DateTimeInput label="Lunch hour blocked" unselectableTimes={d => d.getHours() === 12} defaultValue={new Date()} mobileNative={false} />
Same as Disabled Dates — the OS-native pickers can't evaluate predicates. This example forces mobileNative={false} so the blocked hour works on touch devices too.
Typing-first — the same blocked hour with openOnFocus={false}: setting the hour segment to a blocked hour is vetoed by the unselectableTimes predicate (just as the wheels skip it), and the launcher icon (or Alt+↓) opens the popover.
function example() { return ( <DateTimeInput label="Lunch hour rejected while typing" unselectableTimes={d => d.getHours() === 12} defaultValue={new Date(2024, 5, 7, 11, 30)} mobileNative={false} openOnFocus={false} /> ); }
Inline
<DateTimeInput label="Inline" inline defaultValue={new Date()} />
First Day of Week
<DateTimeInput label="Monday-first" firstDayOfWeek={1} defaultValue={new Date()} mobileNative={false} />
The OS-native calendars use the device locale for the week start, so firstDayOfWeek is ignored there. This example forces mobileNative={false} so the Monday-first grid shows on touch devices too.
Typing-first — the same Monday-first example with openOnFocus={false}: type in the field directly and bring up the popover with the launcher icon (or Alt+↓) to see the week start.
<DateTimeInput label="Monday-first" firstDayOfWeek={1} defaultValue={new Date()} mobileNative={false} openOnFocus={false} />
Mobile Native
By default mobileNative='auto': on touch devices with a small viewport ((pointer: coarse) and (max-width: 768px)) the input swaps to a plain <input type="datetime-local"> so the OS-native picker handles the interaction. Pass true or false to override.
<DateTimeInput label="Native datetime-local" mobileNative={true} />
The OS-native fallback is just a <input type="datetime-local">, so it inherits each platform's behavior. The custom popover (mobileNative={false}) honors every prop on every device.
Honored on Android Chrome but NOT on iOS Safari:
min/max— Android Chrome dims out-of-range values in the picker; iOS lets the user pick any value, only firing the constraint at form-submission validation. (WebKit bug #225639, still open as of 2026.)incrementMinutes,incrementHours— Android Chrome respectsstep(e.g. only 0/15/30/45 minutes selectable whenstep=900). iOS shows every value regardless.enableSeconds— Android Chrome shows a seconds component whenstep < 60(with some long-standing quirks fordatetime-local). iOS has no seconds wheel under any circumstances.
Ignored on BOTH iOS Safari and Android Chrome (HTML-spec gaps):
shouldDisableDate,unselectableDates,unselectableTimes— HTML has no predicate/array equivalent; native pickers can't evaluate functions.firstDayOfWeek,dayNames,monthNames,nearbyMonthDays— both use the device's system locale.hourFormat— both use the device's system clock setting (12h/24h).format,locale— both use the device's system locale; per-input overrides are ignored.placeholder— neither renders placeholder text.
If any of these matter, pass mobileNative={false} to force the custom popover (works on every device), or duplicate the constraint in onChange / server-side validation.
Locale
<Block display="flex" flexDirection="column"> <DateTimeInput label="ja-JP" locale="ja-JP" defaultValue={new Date()} mobileNative={false} /> <DateTimeInput label="fr-FR" locale="fr-FR" defaultValue={new Date()} mobileNative={false} /> </Block>
The OS-native pickers always use the device's system locale, so these examples set mobileNative={false} to show the per-input locale on touch devices too.
Typing-first — the same locales with openOnFocus={false} on each instance: focus to type the localized value, and use the launcher icon (or Alt+↓) to open the popover.
<Block display="flex" flexDirection="column"> <DateTimeInput label="ja-JP" locale="ja-JP" defaultValue={new Date()} mobileNative={false} openOnFocus={false} /> <DateTimeInput label="fr-FR" locale="fr-FR" defaultValue={new Date()} mobileNative={false} openOnFocus={false} /> </Block>
Sizes
<Block display="flex" flexDirection="column"> <DateTimeInput label="Small" controlSize="small" size="small" /> <DateTimeInput label="Default" /> <DateTimeInput label="Medium" controlSize="medium" size="medium" /> <DateTimeInput label="Large" controlSize="large" size="large" /> </Block>
Typing-first — every size with openOnFocus={false}: clicking any field lets you type straight away, with the launcher icon (or Alt+↓) opening the popover.
<Block display="flex" flexDirection="column"> <DateTimeInput label="Small" controlSize="small" size="small" openOnFocus={false} /> <DateTimeInput label="Default" openOnFocus={false} /> <DateTimeInput label="Medium" controlSize="medium" size="medium" openOnFocus={false} /> <DateTimeInput label="Large" controlSize="large" size="large" openOnFocus={false} /> </Block>
Colors
<Block display="flex" flexDirection="column"> <DateTimeInput label="Primary" color="primary" /> <DateTimeInput label="Info" color="info" /> <DateTimeInput label="Success" color="success" /> <DateTimeInput label="Warning" color="warning" /> <DateTimeInput label="Danger" color="danger" /> </Block>
Typing-first — the same colors with openOnFocus={false} on every instance: type directly in any field and open the popover with the launcher icon (or Alt+↓).
<Block display="flex" flexDirection="column"> <DateTimeInput label="Primary" color="primary" openOnFocus={false} /> <DateTimeInput label="Info" color="info" openOnFocus={false} /> <DateTimeInput label="Success" color="success" openOnFocus={false} /> <DateTimeInput label="Warning" color="warning" openOnFocus={false} /> <DateTimeInput label="Danger" color="danger" openOnFocus={false} /> </Block>
Inline: rendered inline, the panel shows the color without opening a popover. The calendar takes it on the selected date, today's date and its keyboard focus ring; open the time row and the wheels take it on their selection band. The footer's time pill and Done button stay primary.
<DateTimeInput label="Danger" color="danger" inline defaultValue={new Date()} />
States
<Block display="flex" flexDirection="column"> <DateTimeInput label="Disabled" disabled /> <DateTimeInput label="Read only" readOnly defaultValue={new Date()} /> <DateTimeInput label="Loading" isLoading /> </Block>
Context-Aware Rendering
Default (with label)
<DateTimeInput label="When" placeholder="YYYY-MM-DD HH:MM" />
With Field Wrapper
function example() { return ( <Field horizontal label="When"> <Field.Body> <Field> <DateTimeInput placeholder="YYYY-MM-DD HH:MM" /> </Field> </Field.Body> </Field> ); }
With Field and Control Wrappers
function example() { return ( <Field horizontal label="When"> <Field.Body> <Field> <Control iconLeftName="calendar-alt"> <DateTimeInput placeholder="YYYY-MM-DD HH:MM" /> </Control> </Field> </Field.Body> </Field> ); }
Inside a Control, DateTimeInput renders no Control of its own, so the props it would hand one, such as isLoading, the icon props, controlSize and controlClassName, do nothing there and warn in development. Set them on that Control instead, as iconLeftName is above. Its default left icon is left out there too, without a warning, since you did not set it. An inline picker renders no Control anywhere, so these props do nothing on it inside a Control or out, and it warns about them too.
Inside a Control with no Field around it, DateTimeInput renders no Field of its own either, unless you give it label, message, horizontal or fieldClassName. Those need a Field, so with any of them it keeps its own Field inside the Control and warns in development. Wrap the Control in a Field, as above, and set the label, horizontal and class name on that Field instead.
Manual Keyboard Entry
The single input spans the whole date-time: year → month → day → hours → minutes (plus seconds / AM-PM when enabled). Focus highlights the year; ↑ / ↓ adjust a segment, → / ← move between them, digits overwrite with auto-advance, and typing a -, space, or : jumps across the separators. Segment mode activates whenever format is a token string with padded tokens (YYYY, MM, DD, HH/hh, mm, ss, A); Intl.DateTimeFormatOptions formats and single-character tokens fall back to free-form text.
These examples use openOnFocus={false} so the popover doesn't cover the input.
With openOnFocus={false} (used here), clicking the field just lets you type — the popover does not appear on focus or click. Open the picker by clicking the launcher icon on the right (or pressing Alt+↓). With the default openOnFocus={true}, focusing or clicking the field opens the popover immediately (you can still type while it's open).
Basic
function example() { return ( <DateTimeInput label="Type across date and time" defaultValue={new Date(2024, 5, 7, 13, 45)} openOnFocus={false} /> ); }
Controlled with live value
function example() { const [v, setV] = useState(() => new Date(2024, 5, 7, 13, 45)); return ( <Block> <DateTimeInput label="Arrow or type — value updates live" value={v} onChange={setV} openOnFocus={false} /> <Paragraph mt="2">Selected: {v ? v.toString() : '—'}</Paragraph> </Block> ); }
Free-form fallback
An Intl.DateTimeFormatOptions format has no segment map, so entry is free-form: focusing does not highlight a segment.
function example() { return ( <DateTimeInput label="Free-form (Intl format)" format={{ dateStyle: 'medium', timeStyle: 'short' }} defaultValue={new Date(2024, 5, 7, 13, 45)} openOnFocus={false} /> ); }
Picker vs Input Modes
editable controls whether segmented typing is allowed; popover controls whether the calendar + time panel exists. Both default to true.
editable | popover | Behavior |
|---|---|---|
true | true | Both — segmented typing + popover (default) |
false | true | Picker-only — typing inert, popover opens |
true | false | Input-only — segmented typing, no popover |
false | false | Static display |
Picker only
<DateTimeInput label="Picker only" editable={false} defaultValue={new Date()} />
Input only
<DateTimeInput label="Input only" popover={false} defaultValue={new Date()} />
Keyboard Navigation
On the input (segmented entry)
A single field spans year → month → day → hours → minutes (→ seconds → AM/PM when enabled). Focus highlights the year; segment mode activates whenever format is a token string with padded tokens (YYYY, MM, DD, HH, hh, mm, ss, A); Intl formats and single-character tokens fall back to free-form text entry.
| Key | Action |
|---|---|
↑ / ↓ | Increment / decrement the active segment (wraps in place) |
Alt+↓ / Alt+↑ | Open / close the popover, leaving the segment as it is |
← / → | Move to previous / next segment |
0–9 | Overwrite the active segment; auto-advances when no further digit is valid |
a / A / p / P | Toggle AM/PM on the meridiem segment (12-hour formats) |
Separator (- / : . space) | Skip to the next segment without inserting the character |
Backspace | Clear the typed-digit buffer; if already cleared, move to the previous segment |
Tab | Clear segment selection so focus moves out naturally |
Escape | Close the popover |
In free-form entry there is no segment to step, so a plain ↓ opens the popover as well.
On the popover
| Key | Action |
|---|---|
Escape | Close popover |
← / → | Move focused date by ±1 day |
↑ / ↓ | Move focused date by ±7 days |
PageUp / PageDown | Move focused date by ±1 month |
Shift+PageUp/Down | Move focused date by ±1 year |
Home / End | Jump to start / end of week |
Enter / Space | Select focused date |
Tab | Move focus from calendar → time button → footer |
Clicking the month and year in the calendar's header opens the year list to jump to another year. There ← / → move focus by a year, ↑ / ↓ by a row, Home / End go to the list's ends, and Enter / Space jump to the focused year. The calendar stays on its month until a year is picked, and Escape goes back to it without jumping (a second Escape closes the popover).
Activate the footer time button (Enter / Space) to float the wheels over the calendar, with focus on the hours wheel. On a time wheel: ↑ / ↓ raise / lower the value, PageUp / PageDown by 5, Home / End jump to the lowest / highest, ← / → move between the hours / minutes / (seconds) columns, and Enter commits and closes. While the wheels are open, Escape collapses them (a second Escape closes the popover), and clicking anywhere outside the wheel card dismisses them. Either way focus goes back to the time button, unless you had moved it on to another control, such as a calendar day, where it stays.
Form Submission
| Prop | Description |
|---|---|
name | Form field name. |
form | Optional id of the form the input belongs to. |
required | Marks the field as required for native HTML form validation. |
function DateTimeInputFormDemo() { const [submitted, setSubmitted] = React.useState(''); return ( <form onSubmit={e => { e.preventDefault(); const fd = new FormData(e.currentTarget); setSubmitted(JSON.stringify(Array.from(fd.entries()), null, 2)); }} > <DateTimeInput name="when" label="When" required /> <div style={{ marginTop: '1rem' }}> <button type="submit" className="button is-primary"> Submit </button> </div> {submitted && <pre style={{ marginTop: '1rem' }}>{submitted}</pre>} </form> ); }
Accessibility
- Trigger uses
role="combobox"witharia-haspopup="dialog",aria-expanded, andaria-controls. - Popover panel has
role="dialog"with an accessible name. Opening it puts focus on the focused date. - Closing the popover with
Escape, the ✓ button orEnteron a time wheel returns focus to the input, whether the input or the launcher opened it. UnderopenOnFocusthat returning focus leaves the popover closed; focusing or clicking the input again opens it. Closing it commits nothing by itself: an empty field stays empty, and leaving afterwards commits only what you typed, so seconds the display leaves out are kept. - Calendar uses
role="grid"; cells exposearia-selected,aria-disabled, andaria-current="date". - Roving
tabindexkeeps a single day focusable at a time, and focus moves with it, inline as in the popover. When the focused date is disabled, that cell is the nearest enabled day of the month. - Each time wheel uses
role="spinbutton"witharia-valuemin,aria-valuemax,aria-valuenow, andaria-valuetext. - The footer's confirm button exposes an accessible label (
Done); the Reset button reverts your edits to the value the popover opened with. - Tab order naturally walks from calendar → time wheels → footer (Reset / ✓).
Related Components
Additional Resources
Pair DateTimeInput with closeOnSelect={false} (the default) so users can tweak both date and time before committing via OK — closing on the first date click would surprise them.
Props
The DateTimeInput prop set is the union of DateInput and TimeInput props. Notable additions and overrides:
| Prop | Type | Default | Description |
|---|---|---|---|
label | React.ReactNode | — | Field label. Automatically associated with the input via htmlFor — uses your id when provided, otherwise a generated one. Not wired in inline mode (no visible input to label) and dropped inside an outer Field. |
labelSize | 'small' | 'normal' | 'medium' | 'large' | — | Size for the label. |
labelProps | React.LabelHTMLAttributes<HTMLLabelElement> & { [key: string]: unknown; } | — | Props for the label element. An explicit htmlFor here overrides the automatic association (no id is generated then). |
horizontal | boolean | false | Render the field with horizontal layout. |
iconLeft | IconProps | React.ReactNode | — | Icon props for the left icon. Bulma gives control icons pointer-events: none, so a clickable node here never receives a click. Put a button beside the input in its own addon Control instead. |
iconRight | IconProps | React.ReactNode | — | Icon props for the right icon. Bulma gives control icons pointer-events: none, so a clickable node here never receives a click. Put a button beside the input in its own addon Control instead. |
iconRightName | string | — | Shortcut for the right icon name. |
iconLeftSize | 'small' | 'medium' | 'large' | — | Shortcut for left icon size. |
iconRightSize | 'small' | 'medium' | 'large' | — | Shortcut for right icon size. |
hasIconsLeft | boolean | false | Force the left icon container. |
hasIconsRight | boolean | false | Force the right icon container. |
isLoading | boolean | false | Shows a loading spinner on the Control it renders, and hides the launcher (triggerIcon) while it does. Inside your own Control it renders none, so this draws nothing and warns in development; set isLoading on that Control. Under prefers-reduced-motion: reduce the spinner stops and stays drawn (with bestax's CSS loaded). |
triggerIcon | boolean | true | Show a clickable launcher button on the right that toggles the popover. Hidden by default while a spinner shows at the same right edge: this component's isLoading when it renders its own Control, or the enclosing Control's isLoading inside one. |
isExpanded | boolean | false | Expand the control to fill its container. |
controlSize | 'small' | 'medium' | 'large' | — | Size of the wrapping Control. |
message | React.ReactNode | — | Help/validation text below the input. |
messageColor | 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | — | Message color. |
fieldClassName | string | — | Additional CSS classes for the Field wrapper. |
controlClassName | string | — | Additional CSS classes for the Control wrapper. |
value | Date | null | — | Controlled selected date-time. |
defaultValue | Date | null | — | Initial value for uncontrolled usage. |
onChange | (d: Date | null) => void | — | Fired when either the date or time portion changes. Picking a day keeps the value's time of day, seconds included, and an empty field's day is picked at midnight. |
onOpen | () => void | — | Fired when the popover opens. |
onClose | () => void | — | Fired when the popover closes. |
min | Date | — | Lower bound for the combined date-time. A min before year 1 is raised to midnight on 1 January of year 1, where the range starts without one too: HTML's datetime-local input holds no earlier year, so the calendar, the time wheels and typing stop there. |
max | Date | — | Upper bound for the combined date-time. |
disabled | boolean | false | Disable the input. |
readOnly | boolean | false | Read-only input. |
placeholder | string | — | Placeholder text. |
format | Intl.DateTimeFormatOptions | string | 'YYYY-MM-DD HH:mm' | Token format string or Intl.DateTimeFormat options. Default 'YYYY-MM-DD HH:mm'. |
parse | (s: string) => Date | null | — | Custom parser. Enter and leaving the field call it only if the user changed the text, so focus passing through commits nothing and the value keeps what the format leaves out, such as seconds. |
locale | string | — | BCP-47 locale tag. |
inline | boolean | false | Render the panel inline (no popover). |
mobileNative | boolean | 'auto' | 'auto' | Use <input type="datetime-local"> on coarse-pointer devices. |
editable | boolean | true | Allow segmented keyboard typing (type the date-time directly across all segments). false makes the field picker-only. |
popover | boolean | true | Whether the calendar + time popover exists. false makes the field input-only (segmented typing with no popover). Default true. |
openOnFocus | boolean | true | Open the popover on focus. Default true. Focus that a closing popover hands back to the input leaves it closed. Dismissing it commits nothing: an empty field stays empty, and leaving afterwards commits only what was typed since. With it off, the launcher or Alt+ArrowDown opens it, as ArrowDown alone steps the active segment. |
closeOnSelect | boolean | false | Off by default — users typically tweak both halves before committing. |
position | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | 'auto' | 'bottom-left' | Popover anchor position. |
appendToBody | boolean | false | Render the popover into document.body via portal. |
color | 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | — | Bulma color modifier for the input, also carried by the calendar and the time wheels, where it colors the selected date and the selection band. Today's date and the calendar's keyboard focus ring take the color's -on-scheme variant, which Bulma adjusts to contrast with the background, so pale colors stay readable; that makes 'primary' a shade off the unset calendar, which uses plain primary for them. A focused wheel's ring is drawn inside the band in the color's -invert, like the selected value, so it shows on the fill. Unset, they use their --bulma-dateinput-* and --bulma-timeinput-wheel-* variables, which follow primary by default. The footer's time pill and Done button stay primary either way. |
size | 'small' | 'medium' | 'large' | — | Size variant. |
isRounded | boolean | false | Rounded input corners. |
shouldDisableDate | (d: Date) => boolean | — | Disable specific dates. Blocked dates are also rejected during manual typing (the predicate receives the full candidate date-time — prefer day-based checks). |
unselectableDates | Date[] | — | Convenience array of disabled dates, matched by calendar day; also rejected by manual typing. |
firstDayOfWeek | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 | Day the calendar week starts on. |
dayNames | string[] | — | Override the 7 day-name labels. |
monthNames | string[] | — | Override the 12 month-name labels. |
nearbyMonthDays | boolean | true | Show dimmed dates from adjacent months. |
hourFormat | '12' | '24' | '24' | Time format. |
enableSeconds | boolean | false | Show seconds column. Note: iOS Safari's native datetime-local picker UI does not include a seconds wheel; pass mobileNative={false} if you need one on iOS. |
incrementHours | number | 1 | Hour step. |
incrementMinutes | number | 1 | Step between minute-wheel values (1 = every minute, iOS-style). |
incrementSeconds | number | 1 | Step between second-wheel values (only with enableSeconds). |
unselectableTimes | (d: Date) => boolean | — | Block specific times. Blocked times are also rejected during manual typing. |
iconLeftName | string | 'calendar-alt' | Decorative left icon glyph for the wrapping Control (shown by default). Set '' to hide. |
triggerIconName | string | 'chevron-down' | Glyph for the right launcher button. |
audioTick | boolean | false | Play a short audible tick on each time-wheel crossing. Default false. |
haptics | boolean | false | Auto-route tactile feedback per wheel tick (vibrate on Android, audio thunk on iOS). Default false. |
labels | PickerLabels | — | Optional translatable string overrides. |
className | string | — | Additional CSS classes. |
ref | React.Ref<HTMLInputElement> | — | Forwarded to the underlying element. |
name | string | — | Form field name. |
form | string | — | Optional id of the form the input belongs to. |
required | boolean | false | Marks the field as required for native HTML form validation. |
... | All standard <input> attributes and Bulma helper props | — | See Helper Props |
When you pass an explicit token format, that format is the source of truth for the time wheels and the footer time pill: a 12-hour format (h/hh with A/a) drives a 12-hour wheel with an AM/PM column, and a 24-hour format (H/HH) drives a 24-hour wheel — regardless of hourFormat. So hourFormat only applies when you don't pass a format. (If format is an Intl.DateTimeFormat options object rather than a token string, the cycle can't be read from it and the wheel/pill fall back to hourFormat.)
CSS & Sass Variables
DateTimeInput registers these variables on its own .input element. Override them there (or via className) — a value set on an ancestor is only inherited, and loses to the component-level declaration. See Theme.
| CSS Variable | Sass Variable | Default |
|---|---|---|
--bulma-dateinput-min-width ‡ | $dateinput-min-width | 16rem |
--bulma-dateinput-cell-size ‡ | $dateinput-cell-size | 2.25rem |
--bulma-dateinput-cell-radius ‡ | $dateinput-cell-radius | var(--bulma-radius-small) |
--bulma-dateinput-cell-color ‡ | $dateinput-cell-color | var(--bulma-text) |
--bulma-dateinput-cell-hover-bg ‡ | $dateinput-cell-hover-bg | hsla(0, 0%, 50%, 0.13) |
--bulma-dateinput-cell-selected-bg ‡ | $dateinput-cell-selected-bg | var(--bulma-primary) |
--bulma-dateinput-cell-selected-color ‡ | $dateinput-cell-selected-color | var(--bulma-primary-invert) |
--bulma-dateinput-cell-today-color ‡ | $dateinput-cell-today-color | var(--bulma-primary) |
--bulma-dateinput-focus-ring-color ‡ | $dateinput-focus-ring-color | var(--bulma-primary) |
--bulma-dateinput-cell-disabled-color ‡ | $dateinput-cell-disabled-color | var(--bulma-text-weak) |
--bulma-dateinput-cell-other-month-color ‡ | $dateinput-cell-other-month-color | var(--bulma-text-weak) |
--bulma-dateinput-header-padding ‡ | $dateinput-header-padding | 0.5rem 0 |
--bulma-dateinput-day-name-color ‡ | $dateinput-day-name-color | var(--bulma-text-weak) |
--bulma-dateinput-day-name-size ‡ | $dateinput-day-name-size | var(--bulma-size-7) |
--bulma-dateinput-nav-button-size ‡ | $dateinput-nav-button-size | 1.75rem |
--bulma-datetimeinput-gap ‡ | $datetimeinput-gap | 0.75rem |
--bulma-datetimeinput-time-card-background ‡ | $datetimeinput-time-card-background | var(--bulma-scheme-main) |
--bulma-datetimeinput-time-card-border-color ‡ | $datetimeinput-time-card-border-color | var(--bulma-border) |
--bulma-datetimeinput-time-card-radius ‡ | $datetimeinput-time-card-radius | var(--bulma-radius-large) |
--bulma-datetimeinput-time-card-shadow ‡ | $datetimeinput-time-card-shadow | 0 8px 24px hsla(0, 0%, 0%, 0.18) |
--bulma-datetimeinput-time-card-padding ‡ | $datetimeinput-time-card-padding | 0.5rem 0.75rem |
--bulma-datetimeinput-time-overlay-background ‡ | $datetimeinput-time-overlay-background | hsla(var(--bulma-scheme-h), var(--bulma-scheme-s), var(--bulma-scheme-main-l), 0.55) |
--bulma-datetimeinput-time-overlay-blur ‡ | $datetimeinput-time-overlay-blur | 3px |
--bulma-picker-popover-z-index ‡ | $picker-popover-z-index | 30 |
--bulma-picker-popover-background ‡ | $picker-popover-background | var(--bulma-scheme-main) |
--bulma-picker-popover-radius ‡ | $picker-popover-radius | var(--bulma-radius-large) |
--bulma-picker-popover-shadow ‡ | $picker-popover-shadow | 0 8px 24px hsla(0, 0%, 0%, 0.18) |
--bulma-picker-popover-border-color ‡ | $picker-popover-border-color | var(--bulma-border) |
--bulma-picker-popover-padding ‡ | $picker-popover-padding | 0.75rem |
--bulma-picker-popover-offset ‡ | $picker-popover-offset | 4px |
--bulma-picker-popover-animation-duration ‡ | $picker-popover-animation-duration | 0.15s |
--bulma-timeinput-wheel-width ‡ | $timeinput-wheel-width | 3rem |
--bulma-timeinput-wheel-item-height ‡ | $timeinput-wheel-item-height | 2rem |
--bulma-timeinput-wheel-gap ‡ | $timeinput-wheel-gap | 0.4rem |
--bulma-timeinput-wheel-bg ‡ | $timeinput-wheel-bg | transparent |
--bulma-timeinput-wheel-color ‡ | $timeinput-wheel-color | var(--bulma-text) |
--bulma-timeinput-wheel-dim-color ‡ | $timeinput-wheel-dim-color | var(--bulma-text-weak) |
--bulma-timeinput-wheel-hover-bg ‡ | $timeinput-wheel-hover-bg | hsla(0, 0%, 50%, 0.13) |
--bulma-timeinput-wheel-selected-bg ‡ | $timeinput-wheel-selected-bg | var(--bulma-primary) |
--bulma-timeinput-wheel-selected-color ‡ | $timeinput-wheel-selected-color | var(--bulma-primary-invert) |
--bulma-timeinput-wheel-radius ‡ | $timeinput-wheel-radius | var(--bulma-radius) |
--bulma-timeinput-wheel-mask ‡ | $timeinput-wheel-mask | linear-gradient(180deg, transparent 0, black 18%, black 82%, transparent 100%) |
--bulma-timeinput-separator-color ‡ | $timeinput-separator-color | var(--bulma-text-weak) |
--bulma-timeinput-separator-size ‡ | $timeinput-separator-size | var(--bulma-size-5) |
--bulma-timeinput-footer-padding ‡ | $timeinput-footer-padding | 0.5rem 0 0 |
--bulma-input-h | $input-h | var(--bulma-scheme-h) |
--bulma-input-s | $input-s | var(--bulma-scheme-s) |
--bulma-input-l | $input-l | var(--bulma-scheme-main-l) |
--bulma-input-border-style | $input-border-style | solid |
--bulma-input-border-width | $input-border-width | var(--bulma-control-border-width) |
--bulma-input-border-l | $input-border-l | var(--bulma-border-l) |
--bulma-input-border-l-delta | $input-border-l-delta | 0% |
--bulma-input-border-color | $input-border-color | hsl(var(--bulma-input-h), var(--bulma-input-s), calc(var(--bulma-input-border-l) + var(--bulma-input-border-l-delta))) |
--bulma-input-hover-border-l-delta | $input-hover-border-l-delta | var(--bulma-hover-border-l-delta) |
--bulma-input-active-border-l-delta | $input-active-border-l-delta | var(--bulma-active-border-l-delta) |
--bulma-input-focus-h | $input-focus-h | var(--bulma-focus-h) |
--bulma-input-focus-s | $input-focus-s | var(--bulma-focus-s) |
--bulma-input-focus-l | $input-focus-l | var(--bulma-focus-l) |
--bulma-input-focus-shadow-size | $input-focus-shadow-size | var(--bulma-focus-shadow-size) |
--bulma-input-focus-shadow-alpha | $input-focus-shadow-alpha | var(--bulma-focus-shadow-alpha) |
--bulma-input-color-l | $input-color-l | var(--bulma-text-strong-l) |
--bulma-input-background-l | $input-background-l | var(--bulma-scheme-main-l) |
--bulma-input-background-l-delta | $input-background-l-delta | 0% |
--bulma-input-height | $input-height | var(--bulma-control-height) |
--bulma-input-shadow | $input-shadow | inset 0 0.0625em 0.125em hsla(var(--bulma-scheme-h), var(--bulma-scheme-s), var(--bulma-scheme-invert-l), 0.05) |
--bulma-input-placeholder-color | $input-placeholder-color | hsla(var(--bulma-text-h), var(--bulma-text-s), var(--bulma-text-strong-l), 0.3) |
--bulma-input-disabled-color | $input-disabled-color | var(--bulma-text-weak) |
--bulma-input-disabled-background-color | $input-disabled-background-color | var(--bulma-background) |
--bulma-input-disabled-border-color | $input-disabled-border-color | var(--bulma-background) |
--bulma-input-disabled-placeholder-color | $input-disabled-placeholder-color | hsla(var(--bulma-text-h), var(--bulma-text-s), var(--bulma-text-weak-l), 0.3) |
--bulma-input-arrow | $input-arrow | var(--bulma-link) |
--bulma-input-icon-color | $input-icon-color | var(--bulma-text-light) |
--bulma-input-icon-hover-color | $input-icon-hover-color | var(--bulma-text-weak) |
--bulma-input-icon-focus-color | $input-icon-focus-color | var(--bulma-link) |
--bulma-input-radius | $input-radius | var(--bulma-radius) |
‡ declared on a constituent element: values set via className, the style prop, or an ancestor are only inherited and lose — target the declaring element in your CSS.
Item height: --bulma-timeinput-wheel-item-height ($timeinput-wheel-item-height) is registered, and nothing reads it. The component sets each wheel item's height inline, taller on small viewports, because the wheels position their items from that height in script. It stays registered so a Sass build that configures it keeps compiling.