fossui
Components

Time Picker

A field that opens hour, minute, and period wheels in a bottom sheet or a centered card, and commits the time only when the footer confirms.

Try it live

The time picker's trigger is the one FossDatePicker uses, so the two line up in a form. Tapping it opens hour, minute, and period wheels. Unlike the date picker it keeps a draft and fires onChanged only when the footer confirms, since where a wheel happens to stop is not a choice.

Time picker
import 'package:fossui/fossui.dart';

FossTimePicker(
  value: time,
  onChanged: (picked) => setState(() => time = picked),
);

The wheels

Each column is a labelled adjustable: drag it, or focus it and step with the arrow keys. The centre band marks the draft. Nothing reaches onChanged until the confirm action fires, and dismissing the sheet discards the draft.

Time picker wheels
FossTimePicker(
  value: time,
  confirmLabel: 'Set time',
  cancelLabel: 'Discard',
  onChanged: (picked) => setState(() => time = picked),
);

Hour format

use24HourFormat reads and picks on a 24-hour clock, which drops the period column. Leave it null to follow the device setting, which falls back to 12-hour. format overrides the trigger label without touching the wheels, and placeholder is what the trigger shows while nothing is selected.

Time picker hour formats
FossTimePicker(
  value: time,
  use24HourFormat: true,
  placeholder: 'Start time',
  onChanged: (picked) => setState(() => time = picked),
);

Minute step

minuteStep sets the increment the minute wheel offers and must divide 60 evenly. A value that arrives off the grid shows as it is and snaps on the first minute scroll.

Time picker with a coarse minute step
FossTimePicker(
  value: time,
  minuteStep: 15,
  onChanged: (picked) => setState(() => time = picked),
);

Bounds and blocked times

minTime and maxTime are inclusive, and isTimeEnabled blocks individual times on top of them. A blocked time stays on the wheel, dimmed, rather than leaving a hole, and confirming is disabled while the draft rests on one. With any bound in play the wheels stop looping.

FossTimePicker(
  value: time,
  minuteStep: 15,
  minTime: const FossTimeOfDay(hour: 9, minute: 0),
  maxTime: const FossTimeOfDay(hour: 17, minute: 30),
  isTimeEnabled: (t) => !lunch.contains(t),
  onChanged: (picked) => setState(() => time = picked),
);

Presentation

The modal opens as a bottom sheet by default. Pass FossDialogPresentation.centered for a card in the middle of the screen, the same enum the dialog and the date picker use.

FossTimePicker(
  value: time,
  presentation: FossDialogPresentation.centered,
  onChanged: (picked) => setState(() => time = picked),
);

Controlled open state

open puts the modal in controlled mode. Pair it with onOpenChange and rebuild on every change, including a dismissal, or the picker and your state drift apart.

FossTimePicker(
  value: time,
  open: pickerOpen,
  onOpenChange: (open) => setState(() => pickerOpen = open),
  onChanged: (picked) => setState(() => time = picked),
);

Disabled

enabled: false dims the trigger and stops it opening.

Disabled time picker
FossTimePicker(
  value: time,
  enabled: false,
  onChanged: (picked) => setState(() => time = picked),
);

One-off styling

A FossTimePickerStyle tunes the wheel geometry and the placeholder for a single picker. visibleItemCount must be odd and at least 3, so one row sits in the centre band with the same number of neighbours above and below.

FossTimePicker(
  value: time,
  onChanged: (picked) => setState(() => time = picked),
  style: const FossTimePickerStyle(
    itemExtent: 44,
    visibleItemCount: 7,
  ),
);

API

FossTimePicker

PropTypeDefaultDescription
valueFossTimeOfDay?requiredThe committed time, or null for none.
onChangedValueChanged<FossTimeOfDay>requiredCalled with the draft when the footer confirms. Never fires on a scroll.
placeholderString'Pick a time'Shown while nothing is selected, and used as the modal title.
formatString Function(FossTimeOfDay)?nullOverrides the built-in trigger label.
use24HourFormatbool?null24-hour clock, dropping the period column. Null follows the device.
minuteStepint1Minute increment; must divide 60 evenly.
minTimeFossTimeOfDay?nullEarliest selectable time, inclusive.
maxTimeFossTimeOfDay?nullLatest selectable time, inclusive.
isTimeEnabledbool Function(FossTimeOfDay)?nullBlocks individual times on top of the bounds.
openbool?nullControlled open state; pair with onOpenChange.
onOpenChangeValueChanged<bool>?nullFires on every open or close request, dismissals included.
presentationFossDialogPresentationbottomSheetBottom sheet or centered card.
confirmLabelString'Set'Label of the action that commits the draft.
cancelLabelString'Cancel'Label of the action that discards the draft.
enabledbooltrueWhether the trigger accepts input.
semanticsLabelString?nullAccessibility name for the trigger.
styleFossTimePickerStyle?nullPer-instance overrides on the theme.

FossTimeOfDay

A wall-clock time with no date and no time zone, stored on a 24-hour clock. It implements Comparable, so bounds checks and sorting are one call, and it carries value equality.

MemberTypeDescription
FossTimeOfDay({required hour, required minute})constructorhour is 0 to 23 and minute is 0 to 59; both are asserted.
hourintHour on a 24-hour clock, 0 to 23.
minuteintMinute past the hour, 0 to 59.
compareTo(FossTimeOfDay other)intOrders two times by their position in the day.
const opening = FossTimeOfDay(hour: 9, minute: 30);
const closing = FossTimeOfDay(hour: 17, minute: 0);
final isBefore = opening.compareTo(closing) < 0;

FossTimePickerStyle

FieldTypeDefaultDescription
placeholderColorColor?nullColor of the placeholder label.
gapdouble?nullGap between the clock glyph and the trigger label.
itemExtentdouble?nullHeight of one wheel row, before text scaling. Defaults to 36.
visibleItemCountint?nullRows a wheel shows at once. Odd, 3 or more; defaults to 5.
highlightColorColor?nullFill of the centre band behind the selected row.

Live demo

Open the interactive gallery to try the wheels, the minute step, and both presentations with live knobs.

On this page