apps/docs/content/docs/en/react/components/(date-and-time)/time-field.mdx
import { TimeField } from '@heroui/react';
import {TimeField, Label, Description, FieldError} from '@heroui/react';
export default () => (
<TimeField>
<Label />
<TimeField.Group>
<TimeField.Input>
{(segment) => <TimeField.Segment segment={segment} />}
</TimeField.Input>
</TimeField.Group>
<Description />
<FieldError />
</TimeField>
)
TimeField combines label, time input, description, and error into a single accessible component.
Add prefix or suffix icons to enhance the time field.
<ComponentPreview name="time-field-with-prefix-icon" /> <ComponentPreview name="time-field-with-suffix-icon" /> <ComponentPreview name="time-field-with-prefix-and-suffix" />When used inside a Surface component, use variant="secondary" on TimeField.Group to apply the lower emphasis variant suitable for surface backgrounds.
Use isInvalid together with FieldError to surface validation messages.
Control the value to synchronize with other components or state management.
<ComponentPreview name="time-field-controlled" />Complete form example with validation and submission handling.
<ComponentPreview name="time-field-form-example" />TimeField supports validation with minValue, maxValue, and custom validation logic.
<ComponentPreview name="time-field-render-function" />
TimeField has minimal default styling. Override the .time-field class to customize the container styling.
@layer components {
.time-field {
@apply flex flex-col gap-1;
&[data-invalid="true"],
&[aria-invalid="true"] {
[data-slot="description"] {
@apply hidden;
}
}
[data-slot="label"] {
@apply w-fit;
}
[data-slot="description"] {
@apply px-1;
}
}
}
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
.time-field – Root container with minimal styling (flex flex-col gap-1)Note: Child components (Label, Description, FieldError) have their own CSS classes and styling. See their respective documentation for customization options. TimeField.Group styling is documented below in the API Reference section.
TimeField automatically manages these data attributes based on its state:
[data-invalid="true"] or [aria-invalid="true"] - Automatically hides the description slot when invalid[data-required="true"] - Applied when isRequired is true[data-disabled="true"] - Applied when isDisabled is true[data-focus-within="true"] - Applied when any child input is focusedTimeField inherits all props from React Aria's TimeField component.
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | (values: TimeFieldRenderProps) => React.ReactNode | - | Child components (Label, TimeField.Group, etc.) or render function. |
className | string | (values: TimeFieldRenderProps) => string | - | CSS classes for styling, supports render props. |
style | React.CSSProperties | (values: TimeFieldRenderProps) => React.CSSProperties | - | Inline styles, supports render props. |
fullWidth | boolean | false | Whether the time field should take full width of its container |
id | string | - | The element's unique identifier. |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, TimeFieldRenderProps> | - | Overrides the default DOM element with a custom render function. |
| Prop | Type | Default | Description |
|---|---|---|---|
value | TimeValue | null | - | Current value (controlled). Uses @internationalized/date types. |
defaultValue | TimeValue | null | - | Default value (uncontrolled). Uses @internationalized/date types. |
onChange | (value: TimeValue | null) => void | - | Handler called when the value changes. |
placeholderValue | TimeValue | null | - | Placeholder time that influences the format of the placeholder. Defaults to 12:00 AM or 00:00 depending on the hour cycle. |
| Prop | Type | Default | Description |
|---|---|---|---|
isRequired | boolean | false | Whether user input is required before form submission. |
isInvalid | boolean | - | Whether the value is invalid. |
minValue | TimeValue | null | - | The minimum allowed time that a user may select. Uses @internationalized/date types. |
maxValue | TimeValue | null | - | The maximum allowed time that a user may select. Uses @internationalized/date types. |
validate | (value: TimeValue) => ValidationError | true | null | undefined | - | Custom validation function. |
validationBehavior | 'native' | 'aria' | 'native' | Whether to use native HTML form validation or ARIA attributes. |
| Prop | Type | Default | Description |
|---|---|---|---|
granularity | 'hour' | 'minute' | 'second' | 'minute' | Determines the smallest unit displayed in the time picker. |
hourCycle | 12 | 24 | - | Whether to display time in 12 or 24 hour format. By default, determined by locale. |
hideTimeZone | boolean | false | Whether to hide the time zone abbreviation. |
shouldForceLeadingZeros | boolean | - | Whether to always show leading zeros in the hour field. By default, determined by locale. |
| Prop | Type | Default | Description |
|---|---|---|---|
isDisabled | boolean | - | Whether the input is disabled. |
isReadOnly | boolean | - | Whether the input can be selected but not changed. |
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | - | Name of the input element, for HTML form submission. Submits as ISO 8601 string. |
autoFocus | boolean | - | Whether the element should receive focus on render. |
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | - | Accessibility label when no visible label is present. |
aria-labelledby | string | - | ID of elements that label this field. |
aria-describedby | string | - | ID of elements that describe this field. |
aria-details | string | - | ID of elements with additional details. |
TimeField works with these separate components that should be imported and used directly:
@heroui/react@heroui/react@heroui/react@heroui/reactEach of these components has its own props API. Use them directly within TimeField for composition:
import {parseTime} from '@internationalized/date';
import {TimeField, Label, Description, FieldError} from '@heroui/react';
<TimeField
isRequired
isInvalid={hasError}
minValue={parseTime('09:00')}
maxValue={parseTime('17:00')}
value={time}
onChange={setTime}
>
<Label>Appointment Time</Label>
<TimeField.Group>
<TimeField.Input>
{(segment) => <TimeField.Segment segment={segment} />}
</TimeField.Input>
</TimeField.Group>
<Description>Select a time between 9:00 AM and 5:00 PM.</Description>
<FieldError>Please select a valid time.</FieldError>
</TimeField>
TimeField uses types from @internationalized/date:
Time - Time only (hour, minute, second)CalendarDateTime - Date with time but no timezone (TimeField displays only the time portion)ZonedDateTime - Date with time and timezone (TimeField displays only the time portion)Example:
import {parseTime, Time, getLocalTimeZone, now} from '@internationalized/date';
// Parse from string
const time = parseTime('14:30');
// Create from current time
const currentTime = now(getLocalTimeZone());
const timeValue = new Time(currentTime.hour, currentTime.minute, currentTime.second);
// Use in TimeField
<TimeField value={time} onChange={setTime}>
</TimeField>
Note: TimeField uses the
@internationalized/datepackage for time manipulation, parsing, and type definitions. See the Internationalized Date documentation for more information about available types and functions.
When using render props with className, style, or children, these values are available:
| Prop | Type | Description |
|---|---|---|
isDisabled | boolean | Whether the field is disabled. |
isInvalid | boolean | Whether the field is currently invalid. |
isReadOnly | boolean | Whether the field is read-only. |
isRequired | boolean | Whether the field is required. |
isFocused | boolean | Whether the field is currently focused. |
isFocusWithin | boolean | Whether any child element is focused. |
isFocusVisible | boolean | Whether focus is visible (keyboard navigation). |
TimeField.Group accepts all props from React Aria's Group component plus the following:
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Tailwind classes merged with the component styles. |
variant | "primary" | "secondary" | "primary" | Visual variant of the component. primary is the default style with shadow. secondary is a lower emphasis variant without shadow, suitable for use in surfaces. |
TimeField.Input accepts all props from React Aria's DateInput component plus the following:
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Tailwind classes merged with the component styles. |
variant | "primary" | "secondary" | "primary" | Visual variant of the input. primary is the default style with shadow. secondary is a lower emphasis variant without shadow, suitable for use in surfaces. |
The TimeField.Input component accepts a render prop function that receives date segments. Each segment represents a part of the time (hour, minute, second, etc.).
TimeField.Segment accepts all props from React Aria's DateSegment component:
| Prop | Type | Default | Description |
|---|---|---|---|
segment | DateSegment | - | The date segment object from the TimeField.Input render prop. |
className | string | - | Tailwind classes merged with the component styles. |
TimeField.Prefix accepts standard HTML div attributes:
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Tailwind classes merged with the component styles. |
children | ReactNode | - | Content to display in the prefix slot. |
TimeField.Suffix accepts standard HTML div attributes:
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Tailwind classes merged with the component styles. |
children | ReactNode | - | Content to display in the suffix slot. |
The base classes power every instance. Override them once with @layer components.
@layer components {
.date-input-group {
@apply inline-flex h-9 items-center overflow-hidden rounded-field border bg-field text-sm text-field-foreground shadow-field outline-none;
&:hover,
&[data-hovered="true"] {
@apply bg-field-hover;
}
&[data-focus-within="true"],
&:focus-within {
@apply status-focused-field;
}
&[data-invalid="true"] {
@apply status-invalid-field;
}
&[data-disabled="true"],
&[aria-disabled="true"] {
@apply status-disabled;
}
}
.date-input-group__input {
@apply flex flex-1 items-center gap-px rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.date-input-group__segment {
@apply inline-block rounded-md px-0.5 text-end tabular-nums outline-none;
&:focus,
&[data-focused="true"] {
@apply bg-accent-soft text-accent-soft-foreground;
}
}
.date-input-group__prefix,
.date-input-group__suffix {
@apply pointer-events-none shrink-0 text-field-placeholder flex items-center;
}
}
.date-input-group – Root container styling.date-input-group__input – Input wrapper styling.date-input-group__segment – Individual time segment styling.date-input-group__prefix – Prefix element styling.date-input-group__suffix – Suffix element styling:hover or [data-hovered="true"][data-focus-within="true"] or :focus-within[data-invalid="true"] (also syncs with aria-invalid)[data-disabled="true"] or [aria-disabled="true"]:focus or [data-focused="true"] on segment elements[data-placeholder="true"] on segment elements