DateField

Date input field with labels, descriptions, and validation

Usage

<script setup lang="ts">
import { DateField } from '@hareui/vue'
</script>
Date
mm
dd
yyyy
<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 keydata-slotElement
basedate-field<div> root
—labelLabel (iff label / #label)
groupdate-input-group<div role="group"> holding the prefix, segments and suffix
prefixdate-input-group-prefix<span> (iff #prefix)
inputdate-input-group-input<div> wrapping the segments
segmentdate-input-group-segmentone per date part (year, month, day, …)
suffixdate-input-group-suffix<span> (iff #suffix)
—descriptionDescription (iff description / #description)
—field-errorFieldError (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/date objects (CalendarDate, CalendarDateTime or ZonedDateTime) — the same types HeroUI uses.

Examples

With Icons

Date
mm
dd
yyyy
<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>
Date
mm
dd
yyyy
<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>
Date
mm
dd
yyyy
Enter a date
<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 cases
  • secondary - Lower emphasis variant without shadow, suitable for use in surfaces
Primary variant
mm
dd
yyyy
Secondary variant
mm
dd
yyyy
<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.

Date
mm
dd
yyyy
Enter a date
Appointment date
mm
dd
yyyy
Enter a date for your appointment
<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

Birth date
mm
dd
yyyy
Enter your date of birth
Appointment date
mm
dd
yyyy
Enter a date for your appointment
<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

Date
mm
dd
yyyy
Start date
mm
dd
yyyy
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

Date
10
3
2026
This date field is disabled
Date
mm
dd
yyyy
This date field is disabled
<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

Date
mm
dd
yyyy
Date
mm
dd
yyyy
<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.

Date
mm
dd
yyyy
Date
mm
dd
yyyy
<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".

Appointment Date
2
3
2025
<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.

Date
mm
dd
yyyy
Current value: (empty)
<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.

Appointment date
mm
dd
yyyy
Enter a date from today onwards
<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.

Date
mm
dd
yyyy
Enter a date from today onwards
<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.

Date
mm
dd
yyyy
<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

Due date
mm
dd
yyyy
<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 styling
  • prefix → [data-slot="date-input-group-prefix"] – content before the segments, e.g. an icon
  • input → [data-slot="date-input-group-input"] – wrapper around the segments
  • segment → [data-slot="date-input-group-segment"] – each individual date part
  • suffix → [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, prefix and suffix all belong to the internal date-input-group part 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: :focus or [data-focused="true"] on a segment
  • Segment Placeholder: [data-placeholder] on a segment showing its placeholder text

API Reference

Props

PropTypeDefaultDescription
labelstring-Label text rendered above the group.
descriptionstring-Helper text rendered below the group. Hidden while the field is invalid.
errorMessagestring-Error message rendered below the group while invalid. Defaults to the validation errors.
idstringuseId()The id of the field; label, description and error ids derive from it.
namestring-The name of the value, used when submitting a form.
defaultValueDateValue-The initial value when uncontrolled (no v-model).
placeholderDateValue-The placeholder date, used to determine which segments to show when no date is selected. Use v-model:placeholder to control it.
defaultPlaceholderDateValue-The placeholder date when uncontrolled.
granularityGranularity'day' (a `CalendarDate` value), otherwise 'minute'The granularity of the field: which segments are rendered, up to and including this unit.
hourCycleHourCycle-The hour cycle used to format times. Defaults to the locale's preference.
stepDateStep1The stepping interval for the time segments.
stepSnappingbooleanfalseWhether to snap the time value to the nearest step increment after input.
hideTimeZoneboolean-Whether to hide the time zone segment.
minValueDateValue-The minimum selectable date.
maxValueDateValue-The maximum selectable date.
localestring-The locale used to format the field. Defaults to the ConfigProvider locale.
isDateUnavailableMatcher-A function that returns whether a given date is unavailable for selection.
variant"primary" | "secondary"'primary'Visual variant of the group.
fullWidthbooleanfalseWhether the field takes the full width of its container.
disabledbooleanfalseWhether the field is disabled.
invalidbooleanundefinedWhether the field is invalid. Overrides validation when set.
requiredbooleanfalseWhether the field is required.
readonlybooleanfalseWhether the value can be selected but not changed.
validateValidateFn<DateValue>-Validates the committed value. Return an error message (or several) when invalid.
validationBehaviorValidationBehavior'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.
modelValueDateValue | null-The selected date. Bind with v-model.

Slots

SlotPropsDescription
labelanyLabel content. Replaces the label prop.
prefixanyPrefix content, rendered before the segments (e.g. an icon).
suffixanySuffix content, rendered after the segments (e.g. an icon).
descriptionanyDescription content. Replaces the description prop.
errorValidationResultError content, rendered while invalid. Replaces the errorMessage prop.

Emits

EventPayloadDescription
update:modelValue[value: DateValue | null | undefined]-
update:placeholder[value: DateValue]-

Accessibility

  • The label's for points to the field's id (auto-generated with useId() unless id is set)
  • The field's aria-describedby lists the ids of the rendered description (<id>-description) and error (<id>-error)
  • invalid sets aria-invalid="true" on the field; the error is announced via role="alert"
  • Keyboard: ←/→ move between segments, ↑/↓ increment/decrement the focused segment, digits type directly into the focused segment and auto-advance
  • min-value, max-value and is-date-unavailable mark the field invalid on their own, matching React Aria's useDateFieldState — no need to compute invalid yourself just for native-range feedback

On this page

No Headings