TimeField
Segmented time input with label, description, and validation, using @internationalized/date. Reuses av-date-input-group.
Usage
import { AvTimeFieldImports } from '@avesra/angular';import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-basic-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
],
template: `<div av-time-field class="w-[256px]" name="time">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
</div>`,
})
export class TimeFieldBasicDemo {}Anatomy
<div av-time-field>
<label av-label></label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
<p av-description></p>
<p av-field-error></p>
</div>TimeField combines label, segmented time input, description, and error into one accessible field. Compose shared av-label, av-description, and av-field-error parts; the input auto-renders locale-ordered time segments via av-date-input-group.
Examples
With Icons
Add prefix or suffix icons to enhance the time field.
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-time-field-with-prefix-icon-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AppIconComponent,
],
template: `<div av-time-field class="w-[256px]" name="time">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-prefix>
<app-icon icon="solar:clock-circle-linear" size="16" class="text-muted" />
</div>
<div av-date-input-group-input></div>
</div>
</div>`,
})
export class TimeFieldWithPrefixIconDemo {}import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-time-field-with-suffix-icon-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AppIconComponent,
],
template: `<div av-time-field class="w-[256px]" name="time">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
<div av-date-input-group-suffix>
<app-icon icon="solar:clock-circle-linear" size="16" class="text-muted" />
</div>
</div>
</div>`,
})
export class TimeFieldWithSuffixIconDemo {}import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-time-field-with-prefix-and-suffix-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AppIconComponent,
],
template: `<div av-time-field class="w-[256px]" name="time">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-prefix>
<app-icon icon="solar:clock-circle-linear" size="16" class="text-muted" />
</div>
<div av-date-input-group-input></div>
<div av-date-input-group-suffix>
<app-icon icon="solar:alt-arrow-down-linear" size="16" class="text-muted" />
</div>
</div>
</div>`,
})
export class TimeFieldWithPrefixAndSuffixDemo {}Variants
The date input group supports two visual variants:
primary(default) — standard styling with shadow, suitable for most use casessecondary— lower emphasis without shadow, suitable inside Surface
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-variants-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
],
template: `<div class="flex flex-col gap-4">
<div av-time-field class="w-[256px]" name="primary-time">
<label av-label>Primary variant</label>
<div av-date-input-group variant="primary">
<div av-date-input-group-input></div>
</div>
</div>
<div av-time-field class="w-[256px]" name="secondary-time">
<label av-label>Secondary variant</label>
<div av-date-input-group variant="secondary">
<div av-date-input-group-input></div>
</div>
</div>
</div>`,
})
export class TimeFieldVariantsDemo {}In Surface
When used inside a Surface component, use variant="secondary" on av-date-input-group to apply the lower emphasis variant suitable for surface backgrounds.
Enter a time
Enter a time for your appointment
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvDescriptionComponent,
AvLabelComponent,
AvSurfaceComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-time-field-on-surface-demo',
imports: [
AvSurfaceComponent,
AvTimeFieldImports,
AvLabelComponent,
AvDescriptionComponent,
AppIconComponent,
],
host: { class: 'w-full max-w-sm' },
template: `<div av-surface class="flex w-full flex-col gap-4 rounded-3xl p-6">
<div av-time-field class="w-full" name="time">
<label av-label>Time</label>
<div av-date-input-group variant="secondary">
<div av-date-input-group-input></div>
</div>
<p av-description>Enter a time</p>
</div>
<div av-time-field class="w-full" name="time-2">
<label av-label>Appointment time</label>
<div av-date-input-group variant="secondary">
<div av-date-input-group-prefix>
<app-icon icon="solar:clock-circle-linear" size="16" class="text-muted" />
</div>
<div av-date-input-group-input></div>
</div>
<p av-description>Enter a time for your appointment</p>
</div>
</div>`,
})
export class TimeFieldOnSurfaceDemo {}With Description
Enter the start time
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvDescriptionComponent,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-with-description-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AvDescriptionComponent,
],
template: `<div av-time-field class="w-[256px]" name="time">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
<p av-description>Enter the start time</p>
</div>`,
})
export class TimeFieldWithDescriptionDemo {}Required Field
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-required-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
],
template: `<div av-time-field class="w-[256px]" name="time" required>
<label av-label required>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
</div>`,
})
export class TimeFieldRequiredDemo {}Disabled State
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-disabled-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
],
template: `<div av-time-field class="w-[256px]" name="time" disabled>
<label av-label disabled>Time</label>
<div av-date-input-group disabled>
<div av-date-input-group-input></div>
</div>
</div>`,
})
export class TimeFieldDisabledDemo {}Full Width
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-time-field-full-width-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AppIconComponent,
],
template: `<div class="w-[400px] space-y-4">
<div av-time-field full-width name="time">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
</div>
<div av-time-field full-width name="time-icons">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-prefix>
<app-icon icon="solar:clock-circle-linear" size="16" class="text-muted" />
</div>
<div av-date-input-group-input></div>
<div av-date-input-group-suffix>
<app-icon icon="solar:alt-arrow-down-linear" size="16" class="text-muted" />
</div>
</div>
</div>
</div>`,
})
export class TimeFieldFullWidthDemo {}Hour Cycle
Force a 12- or 24-hour clock with hour-cycle. The 24-hour field omits the AM/PM segment.
Uses AM/PM
No AM/PM segment
import { Component } from '@angular/core';
import { parseTime } from '@internationalized/date';
import {
AvTimeFieldImports,
AvDescriptionComponent,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-hour-cycle-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AvDescriptionComponent,
],
template: `<div class="flex flex-col gap-4 sm:flex-row">
<div av-time-field class="w-[256px]" name="time-12" [hour-cycle]="12" [default-value]="value">
<label av-label>12-hour</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
<p av-description>Uses AM/PM</p>
</div>
<div av-time-field class="w-[256px]" name="time-24" [hour-cycle]="24" [default-value]="value">
<label av-label>24-hour</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
<p av-description>No AM/PM segment</p>
</div>
</div>`,
})
export class TimeFieldHourCycleDemo {
readonly value = parseTime('15:30');
}Validation
Use invalid together with av-field-error to surface validation messages.
Please enter a valid time.
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvFieldErrorComponent,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-invalid-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AvFieldErrorComponent,
],
template: `<div av-time-field class="w-[256px]" name="time" invalid required>
<label av-label required invalid>Time</label>
<div av-date-input-group invalid>
<div av-date-input-group-input></div>
</div>
<p av-field-error>Please enter a valid time.</p>
</div>`,
})
export class TimeFieldInvalidDemo {}Controlled
Control the value to synchronize with other components or state management.
Current value: (empty)
import { Component, signal } from '@angular/core';
import { getLocalTimeZone, now, Time, type Time as TimeType } from '@internationalized/date';
import {
AvButtonComponent,
AvTimeFieldImports,
AvDescriptionComponent,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-controlled-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AvDescriptionComponent,
AvButtonComponent,
],
template: `<div class="flex flex-col gap-4">
<div av-time-field class="w-[256px]" name="time" [(value)]="value">
<label av-label>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
<p av-description>Current value: {{ value() ? value()!.toString() : '(empty)' }}</p>
</div>
<div class="flex gap-2">
<button av-button variant="tertiary" type="button" (click)="setNow()">Set now</button>
<button av-button variant="tertiary" type="button" (click)="value.set(null)">Clear</button>
</div>
</div>`,
})
export class TimeFieldControlledDemo {
readonly value = signal<TimeType | null>(null);
setNow(): void {
const t = now(getLocalTimeZone());
this.value.set(new Time(t.hour, t.minute, t.second));
}
}Form Example
Complete form example with validation and submission handling.
import { Component, signal } from '@angular/core';
import { parseTime, type Time as TimeType } from '@internationalized/date';
import {
AvButtonComponent,
AvTimeFieldImports,
AvDescriptionComponent,
AvFieldErrorComponent,
AvFormComponent,
AvLabelComponent,
} from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-time-field-form-example-demo',
imports: [
AvFormComponent,
AvTimeFieldImports,
AvLabelComponent,
AvDescriptionComponent,
AvFieldErrorComponent,
AvButtonComponent,
AppIconComponent,
],
template: `<form av-form class="flex w-[280px] flex-col gap-4" (submit)="onSubmit($event)">
<div
av-time-field
class="w-full"
name="time"
full-width
required
[invalid]="isInvalid()"
[min-value]="minTime"
[max-value]="maxTime"
[(value)]="value"
>
<label av-label required>Appointment time</label>
<div av-date-input-group full-width>
<div av-date-input-group-prefix>
<app-icon icon="solar:clock-circle-linear" size="16" class="text-muted" />
</div>
<div av-date-input-group-input></div>
</div>
@if (isInvalid()) {
<p av-field-error>Time must be between 9:00 AM and 5:00 PM</p>
} @else {
<p av-description>Enter a time between 9:00 AM and 5:00 PM</p>
}
</div>
<button
av-button
class="w-full"
type="submit"
variant="primary"
[disabled]="!value() || isInvalid()"
[pending]="isSubmitting()"
>
{{ isSubmitting() ? 'Submitting...' : 'Submit' }}
</button>
</form>`,
})
export class TimeFieldFormExampleDemo {
readonly minTime = parseTime('09:00');
readonly maxTime = parseTime('17:00');
readonly value = signal<TimeType | null>(null);
readonly isSubmitting = signal(false);
isInvalid(): boolean {
const v = this.value();
return v !== null && (v.compare(this.minTime) < 0 || v.compare(this.maxTime) > 0);
}
onSubmit(event: Event): void {
event.preventDefault();
if (!this.value() || this.isInvalid()) {
return;
}
this.isSubmitting.set(true);
setTimeout(() => {
this.value.set(null);
this.isSubmitting.set(false);
}, 1500);
}
}With Validation
TimeField supports validation with min-value, max-value, and custom invalid state.
Enter a time between 9:00 AM and 5:00 PM
import { Component, signal } from '@angular/core';
import { parseTime, type Time as TimeType } from '@internationalized/date';
import {
AvTimeFieldImports,
AvDescriptionComponent,
AvFieldErrorComponent,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-with-validation-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
AvDescriptionComponent,
AvFieldErrorComponent,
],
template: `<div class="flex flex-col gap-4">
<div
av-time-field
class="w-[256px]"
name="time"
required
[invalid]="isInvalid()"
[min-value]="minTime"
[max-value]="maxTime"
[(value)]="value"
>
<label av-label required>Time</label>
<div av-date-input-group>
<div av-date-input-group-input></div>
</div>
@if (isInvalid()) {
<p av-field-error>Time must be between 9:00 AM and 5:00 PM</p>
} @else {
<p av-description>Enter a time between 9:00 AM and 5:00 PM</p>
}
</div>
</div>`,
})
export class TimeFieldWithValidationDemo {
readonly minTime = parseTime('09:00');
readonly maxTime = parseTime('17:00');
readonly value = signal<TimeType | null>(null);
isInvalid(): boolean {
const v = this.value();
return v !== null && (v.compare(this.minTime) < 0 || v.compare(this.maxTime) > 0);
}
}Customization
Tailwind CSS
import { Component } from '@angular/core';
import {
AvTimeFieldImports,
AvLabelComponent,
} from '@avesra/angular';
@Component({
selector: 'app-time-field-custom-styles-demo',
imports: [
AvTimeFieldImports,
AvLabelComponent,
],
template: `<div av-time-field class="w-[256px]" name="meeting-time">
<label av-label>Meeting time</label>
<div
av-date-input-group
variant="secondary"
class="rounded-xl border border-border/80 bg-default shadow-sm"
>
<div av-date-input-group-input></div>
</div>
</div>`,
})
export class TimeFieldCustomStylesDemo {}Global CSS
To customize the TimeField component classes, you can use the @layer components directive. Learn more .
@layer components {
.av-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;
}
}
}Styling Reference
Avesra follows the BEM methodology so variants and states are reusable and easy to customize.
CSS Classes
.av-time-field— Root container.av-time-field--full-width— Full width modifier.av-date-input-group— Input group shell (shared with DateField).av-date-input-group--primary/--secondary— Variants.av-date-input-group__input— Segment row.av-date-input-group__segment— Editable / literal segment.av-date-input-group__prefix/__suffix— Affixes
Interactive States
[data-invalid="true"]— Hides description when invalid[data-required="true"]— Required field[data-disabled="true"]— Disabled[data-focus-within="true"]— Focus inside segments
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
value / default-value | Time | CalendarDateTime | ZonedDateTime | null | null | Selected time value from @internationalized/date (TimeValue). Implements ControlValueAccessor. |
granularity | 'hour' | 'minute' | 'second' | 'minute' | Smallest unit displayed in the segmented input. |
hour-cycle | 12 | 24 | undefined | Force 12- or 24-hour clock display. |
locale | string | LOCALE_ID | BCP 47 locale for segment order and formatting. |
placeholder-value | Time | CalendarDateTime | ZonedDateTime | null | null | Influences placeholder formatting when value is empty. |
min-value / max-value | Time | CalendarDateTime | ZonedDateTime | null | null | Inclusive time-of-day bounds for committed values. |
disabled / readonly / invalid / required | boolean | false | Interaction and validation states mirrored to data-* attributes. |
full-width | boolean | false | Expands the field to the full width of its container. |
name / id | string | undefined | Hidden time form field name and root element id. |
DateInputGroup variant | 'primary' | 'secondary' | 'primary' | Visual variant for av-date-input-group (shared with DateField). |