DateTimePicker

Types a date and time or picks them from a calendar and a time field.

9
4
2026
8
00
PM

Value: 2026-09-04T20:00

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

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

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

Usage

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

The date time picker combines a minute-precision DateField with a Calendar and a TimeField in a popover: type straight into the segments, or click the button at the end to pick the date in the calendar and adjust the time in the time field. v-model binds a YYYY-MM-DDTHH:mm ISO 8601 string, YYYY-MM-DDTHH:mm:ss at second precision, the same shape as the browser’s native date time input. 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
––
––
AM

Value: empty

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

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

<template>
  <Stack gap="sm" align="start">
    <DateTimePicker
      v-model="at"
      placeholder="2026-09-01T09:00"
      aria-label="Schedule"
      class="w-80"
    />
    <Text tone="muted" size="sm">Value: {{ at ?? 'empty' }}</Text>
  </Stack>
</template>
vue

Examples

Range and unavailable dates

min and max constrain the field with full date times, and the calendar disables dates by their date part; unavailable marks dates as unavailable; minuteStep steps the minute segment in the popover.

9
10
2026
8
00
PM
<script setup lang="ts">
  import { ref } from 'vue'
  import { DateTimePicker } from '@hina-ui/vue'

  const at = ref<string | null>('2026-09-10T20:00')
  const weekend = (value: string) => [0, 6].includes(new Date(`${value}T00:00`).getDay())
</script>

<template>
  <DateTimePicker
    v-model="at"
    min="2026-09-01T00:00"
    max="2026-09-30T23:59"
    :minute-step="15"
    :unavailable="weekend"
    aria-label="Event time"
    class="w-80"
  />
</template>
vue

Clearable

clearable adds a clear button after the segments that empties the value to null.

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

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

<template>
  <DateTimePicker v-model="at" clearable aria-label="Publish time" class="w-80" />
</template>
vue

Sizes

size has three steps, sm, md and lg, matching the input of the same step in height; the calendar, time field and button in the popover use the same step.

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

<template>
  <Stack class="w-80">
    <DateTimePicker size="sm" model-value="2026-09-04T20:00" aria-label="Small" />
    <DateTimePicker size="md" model-value="2026-09-04T20:00" aria-label="Medium" />
    <DateTimePicker size="lg" model-value="2026-09-04T20:00" aria-label="Large" />
  </Stack>
</template>
vue

States

invalid gives the field a warning color, disabled disables the group and readonly makes it read only. variant="secondary" is the flat form used inside a surface.

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

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

In a form

Inside a FormField the label is linked to the input, and the error message is rendered by the field; validation rules and submission belong to the Form.

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

  const schema = v.object({
    startsAt: v.string('Pick a start time'),
  })

  const values = reactive({ startsAt: 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="startsAt" label="Starts at" required>
      <DateTimePicker v-model="values.startsAt" />
    </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 the button at the end opens the popover with focus on the current date. Picking a day updates only the date part, keeps the time part and leaves the popover open; with no value yet, the time part comes from placeholder, or midnight without one.
  • The time field in the popover edits the time part directly. Clicking “OK” closes the popover and returns focus to the button; clicking anywhere outside or pressing Esc closes it too, since every step is already written back.
  • The popover is modal: focus stays inside it while open and the page stops scrolling. Esc inside the time segments does not close the popover; use “OK” or click outside.
  • The popover opens below the field, aligned with its left edge.

Accessibility

  • See DateField, Calendar and TimeField for the semantics of the segments, the calendar and the time field; the time field in the popover carries a localized name.
  • The button that opens the popover carries a localized name and aria-expanded.
  • Name the group with aria-label or aria-labelledby.

API

Props

Prop
Type
Default
Description
modelValue
string | null
null
YYYY-MM-DDTHH:mm, down to ss at second precision
open
boolean
false
Whether the popover is open, v-model:open
placeholder
string
Today
Where the segments and calendar start while empty; its time part is the default time after picking a day
min
string
—
Lower bound of the accepted range
max
string
—
Upper bound of the accepted range
granularity
'minute' | 'second'
'minute'
Precision
hourCycle
12 | 24
Locale
Hour cycle
minuteStep
number
—
Step of the minute segment in the popover
unavailable
(date: string) => boolean
—
Whether a given date is unavailable
weekStartsOn
0 | 1 | 2 | 3 | 4 | 5 | 6
Locale
First day of the week
weekdayFormat
'narrow' | 'short'
'narrow'
Format of the weekday names
fixedWeeks
boolean
true
Whether the calendar always shows six weeks
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 field is disabled
invalid
boolean
false
Whether validation failed
class
string
—
Classes appended to the root element

Slots

Slot
Description
leading
Content of the leading square

Events

Event
Payload
Description
update:modelValue
value: string | null
The value changed
update:open
open: boolean
The popover opened or closed
clear
—
The clear was clicked