Skip to content

PlDateTimePicker

A day and a time, in one popup. Not a date picker that grew a clock, and not a time picker that grew a calendar. The two panels are the same height on purpose.

React
tsx
import { PlDateTimePicker } from 'plass-ui';

<PlDateTimePicker label="Starts" placeholder="Pick a moment" minDate={new Date()} />;
dart
import 'package:plass_ui/plass_ui.dart';

PlDateTimePicker(
  label: const Text('Starts'),
  minDate: DateTime.now(),
  value: starts,
  onChanged: (DateTime? next) => setState(() => starts = next),
);

The panels lift themselves out of the tree, so a picker needs an Overlay above it.

Props

PropTypeDefaultDescription
valueDate | nullThe chosen moment. Use with onValueChange for a controlled picker
defaultValueDate | nullThe moment the picker starts on
onValueChange(value: Date | null) => voidCalled with the new value
minDateDate | nullThe earliest moment that may be chosen. Unlike PlDatePicker, it is read at **full precision**: the day it falls on stays selectable in the calendar and the clock columns block the hours before it
maxDateDate | nullThe latest moment that may be chosen, likewise at full precision
shouldDisableDate(date: Date) => booleanBlocks individual days that are inside the range but still not available
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6Which day the week starts on. Defaults to whatever the locale says; 0 is Sunday
defaultMonthDateWhich month the calendar opens on when there is no value
hour12booleanA 12-hour dial with an AM/PM column. Defaults to whatever the locale does
showSecondsbooleanfalseAdds the seconds column
hourStepnumber1How far apart the rows of each column are
minuteStepnumber1See hourStep
secondStepnumber1See hourStep
shouldDisableTime(value: Date, unit: TimeUnit) => booleanBlocks individual rows. Called once per row per column with the instant that row would produce and the column it is in, so a rule may be as coarse as "no afternoons" or as fine as one minute
showNowButtonbooleantrueOffers the shortcut to this moment in the footer
variantshared'solid' | 'glass' | 'ghost''glass'The material of the trigger, drawn on PlTextField's shell. solid is the well cut into the sheet
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Height and type scale
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'Padding only — never the height, never the type scale
elevationshared0 | 1 | 2 | 30Drop shadow depth of the trigger. The popup has its own, fixed at 3 — it genuinely floats
openbooleanWhether the popup is open. Use with onOpenChange to control it
defaultOpenbooleanfalseWhether the popup starts open
onOpenChange(open: boolean) => voidCalled when the popup opens or closes
localestringBCP 47 tag deciding the month and weekday names, the order of the header's two buttons, and how the trigger writes the value. Defaults to the browser's
formatIntl.DateTimeFormatOptions{ dateStyle: 'medium', timeStyle: 'short' }How the trigger writes the chosen moment. Passed straight to Intl
placeholderReactNodeShown in the trigger while nothing is chosen
clearablebooleanfalseOffers the × that empties the control
closeOnSelectbooleanfalseCloses the popup as soon as a day is chosen. false here and true on PlDatePicker, because a moment is a day *and* a time and closing on the first would leave the second unanswered
labelsPartial<PlPickerLabels>The strings the picker says on its own behalf. Every one has an English default. The dates are never in here: Intl knows those
labelReactNodeLabel above the trigger
descriptionReactNodeHelper text below the trigger
errorReactNodeError message below. Its presence also turns the control invalid
invalidbooleanForces the invalid state without a message
startIconReactNodeThe glyph before the value. A calendar, or a clock, by default
fullWidthbooleanfalseStretches to the width of the container
readOnlybooleanfalseThe value is shown but cannot be changed, and the popup does not open
disabledbooleanfalseUnavailable
requiredbooleanfalseWhether a value must be chosen before the form is submitted
namestringIdentifies the field when a form is submitted, as a local YYYY-MM-DDTHH:MM
classNames{ label?, control?, description?, error?: string }Classes on the parts a className does not reach. control is the part a reader acts on
PropTypeDefaultDescription
value * DateTime?The chosen moment, or null for none
onChangedValueChanged<DateTime?>?Called with the moment that was chosen, or null when it is emptied
openbool?Whether the popup is open. Use with onOpenChange to control it
onOpenChangedValueChanged<bool>?Called when the panels should open or close
defaultMonthDateTime?Which month the calendar opens on when there is no value
minDateDateTime?The earliest moment that may be chosen. Unlike PlDatePicker, it is read at **full precision**: the day it falls on stays selectable in the calendar and the clock columns block the hours before it
maxDateDateTime?The latest moment that may be chosen, likewise at full precision
shouldDisableDatebool Function(DateTime date)?Blocks individual days that are inside the range but still not available
weekStartsOnPlassWeekday?Which day the week starts on. Defaults to whatever the locale says; 0 is Sunday
hour12boolfalseA 12-hour dial with an AM/PM column. A plain false where React takes it from the locale: there is no Intl here to ask, and the words it uses when on are PlDateNames.am and .pm
showSecondsboolfalseAdds the seconds column
hourStepint1How far apart the rows of each column are
minuteStepint1See hourStep
secondStepint1See hourStep
shouldDisableTimebool Function(DateTime value, PlassTimeUnit unit)?Blocks individual rows. Called once per row per column with the instant that row would produce and the column it is in, so a rule may be as coarse as "no afternoons" or as fine as one minute
namesPlDateNamesPlDateNames.englishThe month and weekday names the calendar draws, and the order the header writes them in. **This is what a locale string is in the React build**: there is no Intl in the framework, so the words arrive as an object
labelsPlPickerLabelsPlPickerLabels.englishThe strings the picker says on its own behalf. Every one has an English default. The dates are never in here: Intl knows those
formatValueString Function(DateTime value)?How the trigger writes the value. A callback rather than React's Intl options; without it it is written out of names in its medium form
placeholderWidget?Shown in the trigger while nothing is chosen
clearableboolfalseOffers the × that empties the control
showNowButtonbooltrueOffers the shortcut to this moment in the footer
closeOnSelectboolfalseCloses the popup as soon as a day is chosen. false here and true on PlDatePicker, because a moment is a day *and* a time and closing on the first would leave the second unanswered
variantsharedPlassVariantPlassVariant.glassThe material of the trigger, drawn on PlTextField's shell. solid is the well cut into the sheet
sizesharedPlassSizePlassSize.mdHeight and type scale
colorsharedPlassColorPlassColor.primarySemantic colour role. Arbitrary colour values are not accepted
densitysharedPlassDensityPlassDensity.standardPadding only — never the height, never the type scale
elevationsharedint0Drop shadow depth of the trigger. The popup has its own, fixed at 3 — it genuinely floats
labelWidget?Label above the trigger
descriptionWidget?Helper text below the trigger
errorWidget?Error message below. Its presence also turns the control invalid
invalidbool?Forces the invalid state without a message
startIconWidget?The glyph before the value. A calendar, or a clock, by default
fullWidthboolfalseStretches to the width of the container
readOnlyboolfalseThe value is shown but cannot be changed, and the popup does not open
disabledboolfalseUnavailable
semanticLabelString?The name a screen reader gives a trigger with no visible label
focusNodeFocusNode?Drive focus from outside
autofocusboolfalseTakes focus as it is inserted into the tree

Every native <div> attribute passes straight through to the field wrapper. color is excluded because it collides with the color in the table above, defaultValue because the picker spells it as a value rather than a DOM attribute, and children because the panels are the component.

A className lands on the stack that holds the label, the control and the two lines under it. classNames reaches the four parts inside it: label, control (the trigger) description and error.

The picker is controlled: value with onChanged, and null is a picker with nothing chosen.

The calendar is PlDatePicker's and the columns are PlTimePicker's, both unchanged: everything those two pages say about the words, the header, the columns and the absence of a date library holds here.

One popup

The calendar's grid is seven rows counting its header. The clock's columns are seven of the same cell. They read the same cell ladder for exactly that reason, so the popup is one rectangle rather than two of different sizes pushed together, and switching the calendar to its month or year view does not change that.

The bounds

minDate and maxDate are read at full precision, which is the one place this parts company with PlDatePicker. There, a bound is about which days exist and the time of day on it is ignored. Here, a minimum of 09:30 on the 27th leaves the 27th selectable in the calendar and greys out the morning in the clock.

That is the behaviour a "not before now" rule actually needs, and a day-granular check cannot give it: it would either block the whole of today or allow this morning.

React

Examples

Choosing in either order

Picking a day changes the day and leaves the clock alone; picking an hour changes the clock and leaves the day alone. A picker that reset the time to midnight every time the date was corrected would make choosing a moment an ordered task, and nobody reads a popup in the order it was written.

With no day chosen yet the clock writes onto today, and picking a day afterwards keeps whatever time was set.

closeOnSelect is false here for the same reason: a moment is two answers, so the footer carries a Done.

The step intervals

hourStep, minuteStep and secondStep are PlTimePicker's, unchanged.

React

Names and labels

One locale tag decides the month and weekday names, the order of the header's two buttons, whether the clock is on a 12-hour dial, what AM and PM are called, and how the trigger writes the whole moment.

One names object carries the month and weekday names, the order of the header's two buttons and what AM and PM are called; hour12 and formatValue are the two the framework cannot decide for you. See PlDatePicker for why.

React

readOnly · disabled · error

React

Accessibility

  • The calendar is PlDatePicker's in full (one roving tab stop, full dates as accessible names), and the columns are PlTimePicker's, including the live region that reads the time back as one sentence.

  • The trigger wears the calendar glyph alone, not both: a control cannot say two things at once, and the date is the part a reader scans for.

  • A day blocked by a full-precision bound and an hour blocked by the same bound both carry aria-disabled rather than the attribute, so neither leaves the path a keyboard walks. ::: fw react

  • With name, a hidden input carries the value as a local YYYY-MM-DDTHH:MM, the shape <input type="datetime-local"> submits. Never toISOString(): a picker in Seoul would submit a different day.

:::

Differences from the React build

ReactFlutterWhy
locale / format / a locale-decided hour12names / formatValue / hour12: falseThe same trade PlDatePicker and PlTimePicker explain: there is no Intl in the framework.
value / defaultValue / onValueChangevalue / onChangedFlutter's own controls are controlled.
the hidden input, nameThere is no native form submission to be part of.
className, style, native attributesThere is no class list and no style attribute to pass through.

Released under the MIT License