DateField

Types a date or time segment by segment.

9
4
2026

Value: 2026-09-04

<script setup lang="ts">
  import { ref } from 'vue'
  import { CalendarDays } from '@lucide/vue'
  import { DateField, Stack, Text } from '@hina-ui/vue'

  const date = ref<string | null>('2026-09-04')
</script>

<template>
  <Stack gap="sm" align="start">
    <DateField v-model="date" clearable aria-label="Publish date" class="w-72">
      <template #leading>
        <CalendarDays />
      </template>
    </DateField>
    <Text tone="muted" size="sm">Value: {{ date ?? 'empty' }}</Text>
  </Stack>
</template>
vue

Usage

import { DateField } from '@hina-ui/vue'
ts

The date field splits a date into year, month and day segments. Each segment accepts digits only, moves on to the next once it is complete, and the up and down arrow keys step it. v-model binds an ISO 8601 string: YYYY-MM-DD for dates and YYYY-MM-DDTHH:mm with time, the same shape as the browser’s native date inputs, ready to submit or store. Segment order, separators and placeholders follow the current locale. Attributes it does not declare land on the group element that wraps the segments, so name it with aria-label or aria-labelledby.

mm
dd
yyyy

Value: empty

<script setup lang="ts">
  import { ref } from 'vue'
  import { DateField, Stack, Text } from '@hina-ui/vue'

  const date = ref<string | null>(null)
</script>

<template>
  <Stack gap="sm" align="start">
    <DateField v-model="date" aria-label="Release date" class="w-72" />
    <Text tone="muted" size="sm">Value: {{ date ?? 'empty' }}</Text>
  </Stack>
</template>
vue

Examples

Date and time

granularity sets the precision: the default day is date only, minute adds hour and minute, second adds seconds and hour stops at the hour. hourCycle picks the 12 or 24 hour clock, defaulting to the locale.

9
4
2026
8
00
PM

Value: 2026-09-04T20:00

<script setup lang="ts">
  import { ref } from 'vue'
  import { DateField, Stack, Text } from '@hina-ui/vue'

  const at = ref<string | null>('2026-09-04T20:00')
</script>

<template>
  <Stack gap="sm" align="start">
    <DateField v-model="at" granularity="minute" aria-label="Schedule" class="w-80" />
    <Text tone="muted" size="sm">Value: {{ at ?? 'empty' }}</Text>
  </Stack>
</template>
vue

Range

min and max bound the accepted range; a value outside it puts the field into the invalid state but is still emitted. placeholder sets the date the segments start from while the field is empty, today by default.

10
15
2026
<script setup lang="ts">
  import { ref } from 'vue'
  import { DateField } from '@hina-ui/vue'

  const date = ref<string | null>('2026-10-15')
</script>

<template>
  <DateField
    v-model="date"
    min="2026-09-01"
    max="2026-09-30"
    placeholder="2026-09-01"
    aria-label="Event date"
    class="w-72"
  />
</template>
vue

Clearable

clearable adds a button at the end that empties the value to null and returns focus to the first segment.

9
4
2026
<script setup lang="ts">
  import { ref } from 'vue'
  import { DateField } from '@hina-ui/vue'

  const date = ref<string | null>('2026-09-04')
</script>

<template>
  <DateField v-model="date" clearable aria-label="Publish date" class="w-72" />
</template>
vue

Sizes

size has three steps, sm, md and lg, matching the input of the same step in height.

9
4
2026
9
4
2026
9
4
2026
<script setup lang="ts">
  import { DateField, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-72">
    <DateField size="sm" model-value="2026-09-04" aria-label="Small" />
    <DateField size="md" model-value="2026-09-04" aria-label="Medium" />
    <DateField size="lg" model-value="2026-09-04" aria-label="Large" />
  </Stack>
</template>
vue

States

invalid gives the field a warning color, disabled disables the group and readonly keeps it focusable but not editable. variant="secondary" is the flat form used inside a surface.

9
4
2026
9
4
2026
9
4
2026
9
4
2026
<script setup lang="ts">
  import { DateField, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-72">
    <DateField model-value="2026-09-04" invalid aria-label="Invalid" />
    <DateField model-value="2026-09-04" disabled aria-label="Disabled" />
    <DateField model-value="2026-09-04" readonly aria-label="Read only" />
    <DateField model-value="2026-09-04" variant="secondary" aria-label="Secondary" />
  </Stack>
</template>
vue

In a form

Inside a FormField the label is linked to the whole date input through aria-labelledby, and the description and error message are rendered by the field; validation rules and submission belong to the Form. The value is an ISO string, so it can be compared as a string.

mm
dd
yyyy
<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, DateField, Form, FormField, Text } from '@hina-ui/vue'

  const today = new Date().toISOString().slice(0, 10)

  const schema = v.object({
    birthday: v.pipe(
      v.string('Enter your date of birth'),
      v.check(value => value <= today, 'The date of birth cannot be in the future'),
    ),
  })

  const values = reactive({ birthday: null as string | null })
  const saved = ref('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = JSON.stringify(data)
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField name="birthday" label="Date of birth" required>
      <DateField v-model="values.birthday" />
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">Save</Button>
    <Text v-if="saved" tone="muted" size="sm">Saved: {{ saved }}</Text>
  </Form>
</template>
vue

Behavior

  • Clicking a segment or the blank area of the field starts typing; a click on the blank area focuses the first empty segment.
  • Each segment accepts digits only and moves on once complete; the left and right arrow keys move between segments, up and down step the value and Backspace clears the current segment.
  • A value is emitted only when every segment is filled; clearing any segment makes the value null.
  • Values outside min or max are still emitted and marked invalid.

Accessibility

  • Each segment is role="spinbutton" with a localized name and its current value; separators are hidden from assistive technology.
  • Name the group with aria-label or aria-labelledby.
  • invalid and out-of-range values both set aria-invalid on the group element.

API

Props

Prop
Type
Default
Description
modelValue
string | null
null
ISO 8601 string, precision per granularity
placeholder
string
today
Starting point of the empty segments
min
string
—
Lower bound of the accepted range
max
string
—
Upper bound of the accepted range
granularity
'day' | 'hour' | 'minute' | 'second'
'day'
Precision
hourCycle
12 | 24
locale
Hour cycle
clearable
boolean
false
Whether the clear button is shown
readonly
boolean
false
Whether the field is read only
name
string
—
Form field name
variant
'primary' | 'secondary'
'primary'
Variant
size
'sm' | 'md' | 'lg'
'md'
Size
disabled
boolean
false
Whether the group is disabled
invalid
boolean
false
Whether the field failed validation
class
string
—
Classes appended to the root element

Slots

Slot
Description
leading
Content of the leading adornment
trailing
Content of the trailing adornment

Events

Event
Payload
Description
update:modelValue
value: string | null
The value changed
clear
—
The clear was used