Skip to content

TimeField

A time of day, typed or stepped segment by segment.

Solidtype_heroui.Time_field is Kobalte’s TimeField.Root, with the classes HeroUI’s own timeFieldVariants gives, and its group, input and segments HeroUI’s dateInputGroupVariants. Each part of the time – the hour, the minute, the second and AM or PM – is a segment, typed into or stepped by the arrow keys. Its label names it, its description describes it, its error shows while it is invalid, and it submits its value with a form, by Kobalte’s hidden input.

Its value is Kobalte’s Types.Time, an hour, a minute and a second, made by Solidtype_kobalte.Types.Time.make ~hour:14. ~minute:30. ().

HeroUI’s examples, with HeroUI’s icons from @gravity-ui/icons.

Time
––
:
––
AM
time_field_usage.mlx
module S = Solidtype.Solid
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<Time_field class_="w-[256px]" name="time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
</Time_field>
<Time_field>
<Label />
<Time_field.Group>
<Time_field.Input>
(fun segment -> <Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description />
<Field_error />
</Time_field>
Time
––
:
––
AM
time_field_prefix_icon.mlx
module S = Solidtype.Solid
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<Time_field class_="w-[256px]" name="time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Prefix>
<Gravity.Clock class_="size-4 text-muted" />
</Time_field.Prefix>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
</Time_field>
Time
––
:
––
AM
time_field_suffix_icon.mlx
module S = Solidtype.Solid
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<Time_field class_="w-[256px]" name="time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
<Time_field.Suffix>
<Gravity.Clock class_="size-4 text-muted" />
</Time_field.Suffix>
</Time_field.Group>
</Time_field>
Time
––
:
––
AM
Enter a time
time_field_prefix_suffix.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<Time_field class_="w-[256px]" name="time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Prefix>
<Gravity.Clock class_="size-4 text-muted" />
</Time_field.Prefix>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
<Time_field.Suffix>
<Gravity.Chevron_down class_="size-4 text-muted" />
</Time_field.Suffix>
</Time_field.Group>
<Description>"Enter a time"</Description>
</Time_field>
Time
––
:
––
AM
Enter a time
Appointment time
––
:
––
AM
Enter a time for your appointment
time_field_surface.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Surface = Solidtype_heroui.Surface
module Time_field = Solidtype_heroui.Time_field
let make () =
<Surface class_="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6">
<Time_field class_="w-full" name="time">
<Label>"Time"</Label>
<Time_field.Group variant=`secondary>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"Enter a time"</Description>
</Time_field>
<Time_field class_="w-full" name="time-2">
<Label>"Appointment time"</Label>
<Time_field.Group variant=`secondary>
<Time_field.Prefix>
<Gravity.Clock class_="size-4 text-muted" />
</Time_field.Prefix>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"Enter a time for your appointment"</Description>
</Time_field>
</Surface>
Start time
––
:
––
AM
Enter the start time
End time
––
:
––
AM
Enter the end time
time_field_description.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<div class_="flex flex-col gap-4">
<Time_field class_="w-[256px]" name="time">
<Label>"Start time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"Enter the start time"</Description>
</Time_field>
<Time_field class_="w-[256px]" name="end-time">
<Label>"End time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"Enter the end time"</Description>
</Time_field>
</div>
Time
––
:
––
AM
Appointment time
––
:
––
AM
Required field
time_field_required.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<div class_="flex flex-col gap-4">
<Time_field is_required=true class_="w-[256px]" name="time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
</Time_field>
<Time_field is_required=true class_="w-[256px]" name="appointment-time">
<Label>"Appointment time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"Required field"</Description>
</Time_field>
</div>
Time
12
:
21
AM
This time field is disabled
Time
––
:
––
AM
This time field is disabled
time_field_disabled.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
module Time = Solidtype_kobalte.Types.Time
(* The time now, in the reader's time zone. *)
let now () =
let d = Js.Date.make () in
Time.make ~hour:(Js.Date.getHours d) ~minute:(Js.Date.getMinutes d)
~second:(Js.Date.getSeconds d) ()
let make () =
let time_value = now () in
<div class_="flex flex-col gap-4">
<Time_field
is_disabled=true
class_="w-[256px]"
name="time"
value=(fun () -> Js.Nullable.return time_value)>
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"This time field is disabled"</Description>
</Time_field>
<Time_field is_disabled=true class_="w-[256px]" name="time-empty">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>"This time field is disabled"</Description>
</Time_field>
</div>
Time
––
:
––
AM
Time
––
:
––
AM
time_field_full_width.mlx
module S = Solidtype.Solid
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<div class_="w-[400px] space-y-4">
<Time_field full_width=true name="time">
<Label>"Time"</Label>
<Time_field.Group full_width=true>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
</Time_field>
<Time_field full_width=true name="time-icons">
<Label>"Time"</Label>
<Time_field.Group full_width=true>
<Time_field.Prefix>
<Gravity.Clock class_="size-4 text-muted" />
</Time_field.Prefix>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
<Time_field.Suffix>
<Gravity.Chevron_down class_="size-4 text-muted" />
</Time_field.Suffix>
</Time_field.Group>
</Time_field>
</div>
Time
––
:
––
AM
Please enter a valid time
Time
––
:
––
AM
Time must be within business hours
time_field_invalid.mlx
module S = Solidtype.Solid
module Field_error = Solidtype_heroui.Field_error
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<div class_="flex flex-col gap-4">
<Time_field is_invalid=true is_required=true class_="w-[256px]" name="time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Field_error>"Please enter a valid time"</Field_error>
</Time_field>
<Time_field is_invalid=true class_="w-[256px]" name="invalid-time">
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Field_error>"Time must be within business hours"</Field_error>
</Time_field>
</div>
Time
––
:
––
AM
Current value: (empty)
time_field_controlled.mlx
module S = Solidtype.Solid
module Jsx = Solidtype.Jsx
module Button = Solidtype_heroui.Button
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
module Time = Solidtype_kobalte.Types.Time
(* The time now, in the reader's time zone. *)
let now () =
let d = Js.Date.make () in
Time.make ~hour:(Js.Date.getHours d) ~minute:(Js.Date.getMinutes d)
~second:(Js.Date.getSeconds d) ()
(* A time as @internationalized/date's [Time] writes it, 09:45:00. *)
let to_string t =
let two part =
let n = Option.value (part t) ~default:0. in
(if n < 10. then "0" else "") ^ Js.Float.toString n
in
String.concat ":" [ two Time.hour; two Time.minute; two Time.second ]
let make () =
let value, set_value = S.create_signal None in
<div class_="flex flex-col gap-4">
<Time_field
class_="w-[256px]"
name="time"
value=(fun () -> Js.Nullable.fromOption (S.get value))
on_change=(S.set set_value)>
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Description>
(fun () ->
Jsx.string
("Current value: "
^ match S.get value with Some t -> to_string t | None -> "(empty)"))
</Description>
</Time_field>
<div class_="flex gap-2">
<Button
variant=`tertiary on_click=(fun _ -> S.set set_value (Some (now ())))>
"Set now"
</Button>
<Button variant=`tertiary on_click=(fun _ -> S.set set_value None)>
"Clear"
</Button>
</div>
</div>

HeroUI’s button takes isPending while the time is sent; the port’s button has no pending state yet.

Appointment time
––
:
––
AM
Enter a time between 9:00 AM and 5:00 PM
time_field_form.mlx
module S = Solidtype.Solid
module Show = Solidtype.Show
module Button = Solidtype_heroui.Button
module Description = Solidtype_heroui.Description
module Field_error = Solidtype_heroui.Field_error
module Form = Solidtype_heroui.Form
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
module Time = Solidtype_kobalte.Types.Time
external prevent_default : Dom.event -> unit = "preventDefault" [@@mel.send]
let min_time = Time.make ~hour:9. ~minute:0. ()
let max_time = Time.make ~hour:17. ~minute:0. ()
(* A time's seconds since midnight, which order two times. *)
let seconds t =
let part p = Option.value (p t) ~default:0. in
(part Time.hour *. 3600.) +. (part Time.minute *. 60.) +. part Time.second
let make () =
let value, set_value = S.create_signal None in
let is_submitting, set_is_submitting = S.create_signal false in
let is_invalid () =
match S.get value with
| Some t -> seconds t < seconds min_time || seconds t > seconds max_time
| None -> false
in
let handle_submit e =
prevent_default e;
match S.get value with
| Some t when not (is_invalid ()) ->
S.set set_is_submitting true;
(* Simulate API call *)
ignore
(Js.Global.setTimeout
~f:(fun () ->
Js.log2 "Time submitted:" t;
S.set set_value None;
S.set set_is_submitting false)
1500
: Js.Global.timeoutId)
| Some _ | None -> ()
in
<Form class_="flex w-[280px] flex-col gap-4" on_submit=handle_submit>
<Time_field
is_required=true
class_="w-full"
is_invalid=(fun () -> is_invalid ())
name="time"
min_value=(fun () -> min_time)
max_value=(fun () -> max_time)
value=(fun () -> Js.Nullable.fromOption (S.get value))
on_change=(S.set set_value)>
<Label>"Appointment time"</Label>
<Time_field.Group>
<Time_field.Prefix>
<Gravity.Clock class_="size-4 text-muted" />
</Time_field.Prefix>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Show
when_=(fun () -> is_invalid ())
fallback=(<Description>
"Enter a time between 9:00 AM and 5:00 PM"
</Description>)>
<Field_error>"Time must be between 9:00 AM and 5:00 PM"</Field_error>
</Show>
</Time_field>
<Button
class_="w-full"
is_disabled=(fun () -> Option.is_none (S.get value) || is_invalid ())
type_=`submit
variant=`primary>
<Show when_=(fun () -> not (S.get is_submitting))>"Submit"</Show>
<Show when_=(fun () -> S.get is_submitting)>"Submitting..."</Show>
</Button>
</Form>
Time
––
:
––
AM
Enter a time between 9:00 AM and 5:00 PM
time_field_with_validation.mlx
module S = Solidtype.Solid
module Show = Solidtype.Show
module Description = Solidtype_heroui.Description
module Field_error = Solidtype_heroui.Field_error
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
module Time = Solidtype_kobalte.Types.Time
let min_time = Time.make ~hour:9. ~minute:0. ()
let max_time = Time.make ~hour:17. ~minute:0. ()
(* A time's seconds since midnight, which order two times. *)
let seconds t =
let part p = Option.value (p t) ~default:0. in
(part Time.hour *. 3600.) +. (part Time.minute *. 60.) +. part Time.second
let make () =
let value, set_value = S.create_signal None in
let is_invalid () =
match S.get value with
| Some t -> seconds t < seconds min_time || seconds t > seconds max_time
| None -> false
in
<div class_="flex flex-col gap-4">
<Time_field
is_required=true
class_="w-[256px]"
is_invalid=(fun () -> is_invalid ())
name="time"
min_value=(fun () -> min_time)
max_value=(fun () -> max_time)
value=(fun () -> Js.Nullable.fromOption (S.get value))
on_change=(S.set set_value)>
<Label>"Time"</Label>
<Time_field.Group>
<Time_field.Input>
(fun segment ->
<Time_field.Segment segment=(fun () -> S.get segment) />)
</Time_field.Input>
</Time_field.Group>
<Show
when_=(fun () -> is_invalid ())
fallback=(<Description>
"Enter a time between 9:00 AM and 5:00 PM"
</Description>)>
<Field_error>"Time must be between 9:00 AM and 5:00 PM"</Field_error>
</Show>
</Time_field>
</div>

HeroUI’s Render Function example gives the field render, React’s way to swap the element it renders, which a Solid component has no need of.

class_ is added after HeroUI’s classes.

Reminder timeDaily check-in notification.
––
:
––
AM
time_field_tailwind.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Time_field = Solidtype_heroui.Time_field
let make () =
<Time_field class_="w-full max-w-48 gap-1.5" name="reminder">
<Label class_="font-medium text-foreground">"Reminder time"</Label>
<Description>"Daily check-in notification."</Description>
<Time_field.Group
class_="rounded-xl border border-border/80 bg-surface px-2 py-1 \
shadow-sm ring-1 ring-accent/5 focus-within:border-accent/25 \
focus-within:ring-2 focus-within:ring-accent/15">
<Time_field.Input>
(fun segment ->
<Time_field.Segment
class_="text-foreground" segment=(fun () -> S.get segment)
/>)
</Time_field.Input>
</Time_field.Group>
</Time_field>
  • .time-field, and its full width .time-field--full-width
  • .date-input-group, .date-input-group--primary, .date-input-group--secondary, .date-input-group__input, .date-input-group__segment, .date-input-group__prefix, .date-input-group__suffix
  • the field: [data-invalid="true"], [data-required="true"], [data-disabled="true"]
  • the group: [data-hovered="true"], [data-focus-within="true"], [data-focus-visible="true"]
  • a segment: [data-focused="true"], [data-placeholder="true"], [data-type="literal"]

Each prop beside the name HeroUI’s React component gives it.

React solidtype Type Default Description
value value Types.Time.t Js.Nullable.t Its value, controlled; null controls it empty
defaultValue default_value Types.Time.t Its value at first, kept by itself
onChange on_change Types.Time.t option -> unit Called once every segment has its part, and with None once every segment is erased
placeholderValue placeholder_value Types.Time.t What an arrow key steps an empty segment from
minValue min_value Types.Time.t The earliest time
maxValue max_value Types.Time.t The latest time
granularity granularity [ `hour | `minute | `second ] `minute The smallest part it shows
hourCycle hour_cycle [ `h12 | `h24 ] the locale’s Its clock
shouldForceLeadingZeros should_force_leading_zeros bool the locale’s Whether an hour of one digit has a zero before it
isRequired is_required bool false Whether a form requires it
isInvalid is_invalid bool false Whether it is invalid, its error shown
isDisabled is_disabled bool false Whether it is disabled
isReadOnly is_read_only bool false Whether it keeps its value
name name string Its name in a form
aria-label aria_label string Its name where no label names it
fullWidth full_width bool false Whether it takes its container’s width
className class_ string Added after HeroUI’s classes
onBlur on_blur Dom.focusEvent -> unit Called once focus leaves it for an element outside it
validate, validationBehavior — A field validates nothing itself: it is invalid as the application says
hideTimeZone — Not yet
React solidtype Type Default Description
variant variant [ `primary | `secondary ] `primary Its look
fullWidth full_width bool its field’s Whether it takes its container’s width
className class_ string Added after HeroUI’s classes

TimeField.Input, TimeField.Segment, TimeField.Prefix, TimeField.Suffix

Section titled “TimeField.Input, TimeField.Segment, TimeField.Prefix, TimeField.Suffix”

Each takes class_, added after HeroUI’s class; the input, a function of each segment, rendering it; a segment, the segment it is; a prefix and a suffix, their children.

Original HeroUI documentation: TimeField.

The same HeroUI TimeField twice, under the same props: in solidtype on the left, in React on the right. Below them, every attribute of the two fields: a marked row is one they disagree on.

solidtype — OCaml, on Solid and Kobalte
Time
––
:
––
AM
React — TypeScript, on React Aria
attributesolidtypeReact