DateField
Date input field with labels, descriptions, and validation
Usage
<script setup lang="ts">
import { DateField } from '@hareui/vue'
</script><script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <DateField class="w-[256px]" name="date" label="Date" /></template>Anatomy
DateField renders a Label, a group holding an optional prefix, the segmented date input and an optional suffix, then a Description and a FieldError. The text parts come from props; each can be replaced with a slot.
<template>
<DateField label="…" description="…" error-message="…" :invalid="…">
<template #label /> <!-- Label content -->
<template #prefix /> <!-- content rendered before the segments, e.g. an icon -->
<template #suffix /> <!-- content rendered after the segments -->
<template #description /> <!-- Description content -->
<template #error /> <!-- FieldError content, rendered when invalid -->
</DateField>
</template>ui key | data-slot | Element |
|---|---|---|
base | date-field | <div> root |
| — | label | Label (iff label / #label) |
group | date-input-group | <div role="group"> holding the prefix, segments and suffix |
prefix | date-input-group-prefix | <span> (iff #prefix) |
input | date-input-group-input | <div> wrapping the segments |
segment | date-input-group-segment | one per date part (year, month, day, …) |
suffix | date-input-group-suffix | <span> (iff #suffix) |
| — | description | Description (iff description / #description) |
| — | field-error | FieldError (while invalid, iff error-message / #error / validation errors) |
DateField combines a label, segmented date input, description, and error into a single accessible component. Values are
@internationalized/dateobjects (CalendarDate,CalendarDateTimeorZonedDateTime) — the same types HeroUI uses.
Examples
With Icons
<script setup lang="ts">import { DateField } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <DateField class="w-[256px]" name="date" label="Date"> <template #prefix> <Icon class="size-4 text-muted" icon="gravity-ui:calendar" /> </template> </DateField></template><script setup lang="ts">import { DateField } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <DateField class="w-[256px]" name="date" label="Date"> <template #suffix> <Icon class="size-4 text-muted" icon="gravity-ui:calendar" /> </template> </DateField></template><script setup lang="ts">import { DateField } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <DateField class="w-[256px]" name="date" label="Date" description="Enter a date"> <template #prefix> <Icon class="size-4 text-muted" icon="gravity-ui:calendar" /> </template> <template #suffix> <Icon class="size-4 text-muted" icon="gravity-ui:chevron-down" /> </template> </DateField></template>Variants
The DateField component supports two visual variants:
primary(default) - Standard styling with shadow, suitable for most use casessecondary- Lower emphasis variant without shadow, suitable for use in surfaces
<script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <div class="flex flex-col gap-4"> <DateField class="w-[256px]" name="primary-date" label="Primary variant" variant="primary" /> <DateField class="w-[256px]" name="secondary-date" label="Secondary variant" variant="secondary" /> </div></template>On Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
<script setup lang="ts">import { DateField, Surface } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <Surface class="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6"> <DateField class="w-full" variant="secondary" name="date" label="Date" description="Enter a date" /> <DateField class="w-full" variant="secondary" name="date-2" label="Appointment date" description="Enter a date for your appointment"> <template #prefix> <Icon class="size-4 text-muted" icon="gravity-ui:calendar" /> </template> </DateField> </Surface></template>With Description
<script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <div class="flex flex-col gap-4"> <DateField class="w-[256px]" name="date" label="Birth date" description="Enter your date of birth" /> <DateField class="w-[256px]" name="appointment-date" label="Appointment date" description="Enter a date for your appointment" /> </div></template>Required Field
<script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <div class="flex flex-col gap-4"> <DateField required class="w-[256px]" name="date" label="Date" /> <DateField required class="w-[256px]" name="start-date" label="Start date" description="Required field" /> </div></template>Disabled State
<script setup lang="ts">import { getLocalTimeZone, today } from '@internationalized/date'import { DateField } from '@hareui/vue'</script><template> <div class="flex flex-col gap-4"> <DateField disabled class="w-[256px]" name="date" label="Date" :default-value="today(getLocalTimeZone())" description="This date field is disabled" /> <DateField disabled class="w-[256px]" name="date-empty" label="Date" description="This date field is disabled" /> </div></template>Full Width
<script setup lang="ts">import { DateField } from '@hareui/vue'import { Icon } from '@iconify/vue'</script><template> <div class="w-[400px] space-y-4"> <DateField full-width name="date" label="Date" /> <DateField full-width name="date-icons" label="Date"> <template #prefix> <Icon class="size-4 text-muted" icon="gravity-ui:calendar" /> </template> <template #suffix> <Icon class="size-4 text-muted" icon="gravity-ui:chevron-down" /> </template> </DateField> </div></template>Validation
Set invalid with error-message to control the state yourself, or validate with required, a validate function, or a surrounding Form.
<script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <div class="flex flex-col gap-4"> <DateField invalid required class="w-[256px]" name="date" label="Date" error-message="Please enter a valid date" /> <DateField invalid class="w-[256px]" name="invalid-date" label="Date" error-message="Date must be in the future" /> </div></template>Granularity
granularity controls which segments are rendered, up to and including the given unit. A plain CalendarDate only supports "day"; use a CalendarDateTime or ZonedDateTime value for "hour", "minute" or "second".
<script setup lang="ts">import type { DateValue } from '@internationalized/date'import type { ListBoxItem } from '@hareui/vue'import { parseDate, parseZonedDateTime } from '@internationalized/date'import { DateField, Label, Select, Tooltip } from '@hareui/vue'import { Icon } from '@iconify/vue'import { computed, ref } from 'vue'const granularityOptions: ListBoxItem[] = [ { value: 'day', label: 'Day' }, { value: 'hour', label: 'Hour' }, { value: 'minute', label: 'Minute' }, { value: 'second', label: 'Second' },]const granularity = ref<'day' | 'hour' | 'minute' | 'second'>('day')const selectValue = computed({ get: () => granularity.value, set: (value) => { granularity.value = value as typeof granularity.value },})// Determine appropriate default value based on granularityconst defaultValue = computed<DateValue>(() => granularity.value === 'day' ? parseDate('2025-02-03') : parseZonedDateTime('2025-02-03T08:45:00[America/Los_Angeles]'))</script><template> <div class="flex gap-4"> <DateField class="w-[256px]" :default-value="defaultValue" :granularity="granularity" name="granularity-date" label="Appointment Date" /> <div class="flex flex-col gap-1"> <div class="flex items-center gap-2"> <Label>Granularity</Label> <Tooltip :delay="0"> <template #default> <Icon aria-label="Granularity information" class="size-4 text-muted" icon="gravity-ui:circle-question" /> </template> <template #content> <p> Determines the smallest unit displayed in the date picker. By default, this is "day" for dates, and "minute" for times. </p> </template> </Tooltip> </div> <Select v-model="selectValue" class="w-[110px]" placeholder="Select granularity" variant="secondary" :items="granularityOptions" /> </div> </div></template>Controlled
Bind the value with v-model to synchronize with other components or state management.
<script setup lang="ts">import type { DateValue } from '@internationalized/date'import { getLocalTimeZone, today } from '@internationalized/date'import { Button, DateField } from '@hareui/vue'import { shallowRef } from 'vue'const value = shallowRef<DateValue | null>(null)</script><template> <div class="flex flex-col gap-4"> <DateField v-model="value" class="w-[256px]" name="date" label="Date" :description="`Current value: ${value ? value.toString() : '(empty)'}`" /> <div class="flex gap-2"> <Button variant="tertiary" @click="value = today(getLocalTimeZone())"> Set today </Button> <Button variant="tertiary" @click="value = null"> Clear </Button> </div> </div></template>Form Example
Complete form example with validation and submission handling.
<script setup lang="ts">import type { DateValue } from '@internationalized/date'import { getLocalTimeZone, today } from '@internationalized/date'import { Button, DateField, Form } from '@hareui/vue'import { Icon } from '@iconify/vue'import { computed, ref, shallowRef } from 'vue'const value = shallowRef<DateValue | null>(null)const isSubmitting = ref(false)const todayDate = today(getLocalTimeZone())const isInvalid = computed(() => value.value !== null && value.value.compare(todayDate) < 0)function handleSubmit(e: Event) { e.preventDefault() if (!value.value || isInvalid.value) return isSubmitting.value = true // Simulate API call setTimeout(() => { console.log('Date submitted:', { date: value.value }) value.value = null isSubmitting.value = false }, 1500)}</script><template> <Form class="flex w-[280px] flex-col gap-4" @submit="handleSubmit"> <DateField v-model="value" required class="w-full" :invalid="isInvalid" :min-value="todayDate" name="date" label="Appointment date" error-message="Date must be today or in the future" description="Enter a date from today onwards" > <template #prefix> <Icon class="size-4 text-muted" icon="gravity-ui:calendar" /> </template> </DateField> <Button class="w-full" :disabled="!value || isInvalid" :pending="isSubmitting" type="submit" variant="primary"> {{ isSubmitting ? 'Submitting...' : 'Submit' }} </Button> </Form></template>With Validation
DateField supports validation with min-value, max-value, is-date-unavailable, and custom validation logic.
<script setup lang="ts">import type { DateValue } from '@internationalized/date'import { getLocalTimeZone, today } from '@internationalized/date'import { DateField } from '@hareui/vue'import { computed, shallowRef } from 'vue'const value = shallowRef<DateValue | null>(null)const todayDate = today(getLocalTimeZone())const isInvalid = computed(() => value.value !== null && value.value.compare(todayDate) < 0)</script><template> <div class="flex flex-col gap-4"> <DateField v-model="value" required class="w-[256px]" :invalid="isInvalid" :min-value="todayDate" name="date" label="Date" error-message="Date must be today or in the future" description="Enter a date from today onwards" /> </div></template>Render Function
HeroUI's render prop replaces the root element and lets sub-parts render-prop their own markup. Option A has no compound parts to render-prop into; attributes you pass to DateField fall through to the root element instead.
<script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <!-- HeroUI's render-function demo shows a render prop customizing each sub-part; Option A has no compound parts to render-prop into. Vue's natural attrs fallthrough reaches the same root element instead, so this demo shows an arbitrary attribute landing on the field. --> <DateField data-custom="foo" class="w-[256px]" name="date" label="Date" /></template>Customization
Tailwind CSS
<script setup lang="ts">import { DateField } from '@hareui/vue'</script><template> <DateField class="w-64" name="due-date" label="Due date" variant="secondary" :ui="{ group: 'rounded-xl border border-border/80 bg-default shadow-sm' }" /></template>Global Configuration
app.use(createHareUI({
ui: { dateField: { slots: { group: 'rounded-xl', segment: 'font-mono' } } },
}))Styling Reference
Slots
base→[data-slot="date-field"]– root container (flex flex-col gap-1)group→[data-slot="date-input-group"]– container for the prefix, segments and suffix, with border and background stylingprefix→[data-slot="date-input-group-prefix"]– content before the segments, e.g. an iconinput→[data-slot="date-input-group-input"]– wrapper around the segmentssegment→[data-slot="date-input-group-segment"]– each individual date partsuffix→[data-slot="date-input-group-suffix"]– content after the segments
Note: The child components (Label, Description, FieldError) have their own themes. See their respective pages for customization options.
group,input,segment,prefixandsuffixall belong to the internaldate-input-grouppart shared with TimeField.
Interactive States
DateField sets these data attributes:
- Invalid:
[data-invalid="true"]on the root and the group - Automatically hides the description when invalid - Required:
[data-required="true"]on the root - Shows the label's required asterisk - Disabled:
[data-disabled="true"]on the root,[data-disabled]on the group and each segment - Read Only:
[data-readonly="true"]on the root - Focus Within:
[data-focus-within="true"]on the group - Applied when a segment is focused - Segment Focus:
:focusor[data-focused="true"]on a segment - Segment Placeholder:
[data-placeholder]on a segment showing its placeholder text
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Label text rendered above the group. |
description | string | - | Helper text rendered below the group. Hidden while the field is invalid. |
errorMessage | string | - | Error message rendered below the group while invalid. Defaults to the validation errors. |
id | string | useId() | The id of the field; label, description and error ids derive from it. |
name | string | - | The name of the value, used when submitting a form. |
defaultValue | DateValue | - | The initial value when uncontrolled (no v-model). |
placeholder | DateValue | - | The placeholder date, used to determine which segments to show when no date is selected. Use v-model:placeholder to control it. |
defaultPlaceholder | DateValue | - | The placeholder date when uncontrolled. |
granularity | Granularity | 'day' (a `CalendarDate` value), otherwise 'minute' | The granularity of the field: which segments are rendered, up to and including this unit. |
hourCycle | HourCycle | - | The hour cycle used to format times. Defaults to the locale's preference. |
step | DateStep | 1 | The stepping interval for the time segments. |
stepSnapping | boolean | false | Whether to snap the time value to the nearest step increment after input. |
hideTimeZone | boolean | - | Whether to hide the time zone segment. |
minValue | DateValue | - | The minimum selectable date. |
maxValue | DateValue | - | The maximum selectable date. |
locale | string | - | The locale used to format the field. Defaults to the ConfigProvider locale. |
isDateUnavailable | Matcher | - | A function that returns whether a given date is unavailable for selection. |
variant | "primary" | "secondary" | 'primary' | Visual variant of the group. |
fullWidth | boolean | false | Whether the field takes the full width of its container. |
disabled | boolean | false | Whether the field is disabled. |
invalid | boolean | undefined | Whether the field is invalid. Overrides validation when set. |
required | boolean | false | Whether the field is required. |
readonly | boolean | false | Whether the value can be selected but not changed. |
validate | ValidateFn<DateValue> | - | Validates the committed value. Return an error message (or several) when invalid. |
validationBehavior | ValidationBehavior | 'native' | native blocks form submission and shows errors on commit or submit; aria shows errors in realtime.
Defaults to the surrounding Form. |
ui | (ComponentSlots<{ slots: { base: string[]; }; variants: { fullWidth: { false: { base: string; }; true: { base: string; }; }; }; defaultVariants: { fullWidth: boolean; }; }> & { group?: C; } & Pick<ComponentSlots<{ slots: { base: string[]; inputContainer: string; input: string[]; segment: string[]; prefix: string; suffix: string; }; variants: { variant: { primary: { base: string; }; secondary: { base: string[]; }; }; fullWidth: { false: { base: string; }; true: { base: string; }; }; }; defaultVariants: { fullWidth: boolean; variant: string; }; }>, "input" | "prefix" | "suffix" | "segment">) | - | Per-slot class overrides. group, input, segment, prefix and suffix are the internal date-input-group's. |
modelValue | DateValue | null | - | The selected date. Bind with v-model. |
Slots
| Slot | Props | Description |
|---|---|---|
label | any | Label content. Replaces the label prop. |
prefix | any | Prefix content, rendered before the segments (e.g. an icon). |
suffix | any | Suffix content, rendered after the segments (e.g. an icon). |
description | any | Description content. Replaces the description prop. |
error | ValidationResult | Error content, rendered while invalid. Replaces the errorMessage prop. |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | [value: DateValue | null | undefined] | - |
update:placeholder | [value: DateValue] | - |
Accessibility
- The label's
forpoints to the field's id (auto-generated withuseId()unlessidis set) - The field's
aria-describedbylists the ids of the rendered description (<id>-description) and error (<id>-error) invalidsetsaria-invalid="true"on the field; the error is announced viarole="alert"- Keyboard: ←/→ move between segments, ↑/↓ increment/decrement the focused segment, digits type directly into the focused segment and auto-advance
min-value,max-valueandis-date-unavailablemark the field invalid on their own, matching React Aria'suseDateFieldState— no need to computeinvalidyourself just for native-range feedback







