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.
import { PlDateTimePicker } from 'plass-ui';
<PlDateTimePicker label="Starts" placeholder="Pick a moment" minDate={new Date()} />;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
| Prop | Type | Default | Description |
|---|---|---|---|
| value | Date | null | — | The chosen moment. Use with onValueChange for a controlled picker |
| defaultValue | Date | null | — | The moment the picker starts on |
| onValueChange | (value: Date | null) => void | — | Called with the new value |
| minDate | Date | null | — | 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 |
| maxDate | Date | null | — | The latest moment that may be chosen, likewise at full precision |
| shouldDisableDate | (date: Date) => boolean | — | Blocks individual days that are inside the range but still not available |
| weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | — | Which day the week starts on. Defaults to whatever the locale says; 0 is Sunday |
| defaultMonth | Date | — | Which month the calendar opens on when there is no value |
| hour12 | boolean | — | A 12-hour dial with an AM/PM column. Defaults to whatever the locale does |
| showSeconds | boolean | false | Adds the seconds column |
| hourStep | number | 1 | How far apart the rows of each column are |
| minuteStep | number | 1 | See hourStep |
| secondStep | number | 1 | See hourStep |
| shouldDisableTime | (value: Date, unit: TimeUnit) => boolean | — | 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 |
| showNowButton | boolean | true | Offers 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 |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth of the trigger. The popup has its own, fixed at 3 — it genuinely floats |
| open | boolean | — | Whether the popup is open. Use with onOpenChange to control it |
| defaultOpen | boolean | false | Whether the popup starts open |
| onOpenChange | (open: boolean) => void | — | Called when the popup opens or closes |
| locale | string | — | BCP 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 |
| format | Intl.DateTimeFormatOptions | { dateStyle: 'medium', timeStyle: 'short' } | How the trigger writes the chosen moment. Passed straight to Intl |
| placeholder | ReactNode | — | Shown in the trigger while nothing is chosen |
| clearable | boolean | false | Offers the × that empties the control |
| closeOnSelect | boolean | false | Closes 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 |
| labels | Partial<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 |
| label | ReactNode | — | Label above the trigger |
| description | ReactNode | — | Helper text below the trigger |
| error | ReactNode | — | Error message below. Its presence also turns the control invalid |
| invalid | boolean | — | Forces the invalid state without a message |
| startIcon | ReactNode | — | The glyph before the value. A calendar, or a clock, by default |
| fullWidth | boolean | false | Stretches to the width of the container |
| readOnly | boolean | false | The value is shown but cannot be changed, and the popup does not open |
| disabled | boolean | false | Unavailable |
| required | boolean | false | Whether a value must be chosen before the form is submitted |
| name | string | — | Identifies 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 |
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | DateTime? | — | The chosen moment, or null for none |
| onChanged | ValueChanged<DateTime?>? | — | Called with the moment that was chosen, or null when it is emptied |
| open | bool? | — | Whether the popup is open. Use with onOpenChange to control it |
| onOpenChanged | ValueChanged<bool>? | — | Called when the panels should open or close |
| defaultMonth | DateTime? | — | Which month the calendar opens on when there is no value |
| minDate | DateTime? | — | 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 |
| maxDate | DateTime? | — | The latest moment that may be chosen, likewise at full precision |
| shouldDisableDate | bool Function(DateTime date)? | — | Blocks individual days that are inside the range but still not available |
| weekStartsOn | PlassWeekday? | — | Which day the week starts on. Defaults to whatever the locale says; 0 is Sunday |
| hour12 | bool | false | A 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 |
| showSeconds | bool | false | Adds the seconds column |
| hourStep | int | 1 | How far apart the rows of each column are |
| minuteStep | int | 1 | See hourStep |
| secondStep | int | 1 | See hourStep |
| shouldDisableTime | bool 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 |
| names | PlDateNames | PlDateNames.english | The 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 |
| labels | PlPickerLabels | PlPickerLabels.english | The strings the picker says on its own behalf. Every one has an English default. The dates are never in here: Intl knows those |
| formatValue | String 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 |
| placeholder | Widget? | — | Shown in the trigger while nothing is chosen |
| clearable | bool | false | Offers the × that empties the control |
| showNowButton | bool | true | Offers the shortcut to this moment in the footer |
| closeOnSelect | bool | false | Closes 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 |
| variantshared | PlassVariant | PlassVariant.glass | The material of the trigger, drawn on PlTextField's shell. solid is the well cut into the sheet |
| sizeshared | PlassSize | PlassSize.md | Height and type scale |
| colorshared | PlassColor | PlassColor.primary | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | PlassDensity | PlassDensity.standard | Padding only — never the height, never the type scale |
| elevationshared | int | 0 | Drop shadow depth of the trigger. The popup has its own, fixed at 3 — it genuinely floats |
| label | Widget? | — | Label above the trigger |
| description | Widget? | — | Helper text below the trigger |
| error | Widget? | — | Error message below. Its presence also turns the control invalid |
| invalid | bool? | — | Forces the invalid state without a message |
| startIcon | Widget? | — | The glyph before the value. A calendar, or a clock, by default |
| fullWidth | bool | false | Stretches to the width of the container |
| readOnly | bool | false | The value is shown but cannot be changed, and the popup does not open |
| disabled | bool | false | Unavailable |
| semanticLabel | String? | — | The name a screen reader gives a trigger with no visible label |
| focusNode | FocusNode? | — | Drive focus from outside |
| autofocus | bool | false | Takes 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.
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.
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.
readOnly · disabled · error
Accessibility
The calendar is
PlDatePicker's in full (one roving tab stop, full dates as accessible names), and the columns arePlTimePicker'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-disabledrather than the attribute, so neither leaves the path a keyboard walks. ::: fw reactWith
name, a hidden input carries the value as a localYYYY-MM-DDTHH:MM, the shape<input type="datetime-local">submits. NevertoISOString(): a picker in Seoul would submit a different day.
:::
Differences from the React build
| React | Flutter | Why |
|---|---|---|
locale / format / a locale-decided hour12 | names / formatValue / hour12: false | The same trade PlDatePicker and PlTimePicker explain: there is no Intl in the framework. |
value / defaultValue / onValueChange | value / onChanged | Flutter's own controls are controlled. |
the hidden input, name | — | There is no native form submission to be part of. |
className, style, native attributes | — | There is no class list and no style attribute to pass through. |