GitHubX

TimeField

PreviousNext

A composable time input component with segment-based editing, built on Reka UI. Supports hour, minute, second, and 12/24-hour formats.

      
      <script setup lang="ts">
import { CalendarDateTime, getLocalTimeZone } from "@internationalized/date";
import { TimeField, TimeFieldInput, TimeFieldSeparator } from "@/components/time-field";
import { Clock10Icon } from "lucide-vue-next";
import { ref } from "vue";

const date = ref(new CalendarDateTime(2026, 3, 16, 12, 30, 10));

function format(date: CalendarDateTime) {
  return date.toDate(getLocalTimeZone()).toLocaleString();
}
</script>

<template>
  <div class="flex flex-col gap-4">
    <TimeField v-model="date" granularity="second" :hour-cycle="12" v-slot="{ hour, minute, second, dayPeriod }" class="w-fit">
      <Clock10Icon class="size-4" />
      <TimeFieldInput :segment="hour" />
      <TimeFieldSeparator />
      <TimeFieldInput :segment="minute" />
      <TimeFieldSeparator />
      <TimeFieldInput :segment="second" />
      <TimeFieldInput :segment="dayPeriod" />
    </TimeField>
    <p class="text-sm text-muted-foreground">
      {{ format(date) }}
    </p>
  </div>
</template>
    

Features

  • 12/24-hour formats — Control display with hour-cycle prop
  • Configurable granularity — Show hour only, hour+minute, or include seconds
  • Day period — AM/PM display when using 12-hour format
  • Composable — Flexible slot-based architecture with TimeFieldInput and TimeFieldSeparator

Installation

Install from the Vuzeno registry with the shadcn-vue CLI:

      
      bunx --bun shadcn-vue@latest add https://vuzeno.com/r/time-field.json
    

Dependencies

This component requires @internationalized/date for date/time values.

      
      bun add @internationalized/date
    

Anatomy

  • TimeField — Root component that provides segment data via scoped slots
  • TimeFieldInput — Renders a single segment (hour, minute, second, or dayPeriod)
  • TimeFieldSeparator — Renders a separator between segments (default: :)

Examples

Combining with Calendar

      
      <script setup lang="ts">
import { CalendarDateTime, getLocalTimeZone } from "@internationalized/date";
import { TimeField, TimeFieldInput, TimeFieldSeparator } from "@/components/time-field";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { CalendarIcon, Clock10Icon } from "lucide-vue-next";
import { ref } from "vue";

const date = ref(new CalendarDateTime(2026, 3, 16, 12, 30, 0));

function format(date: CalendarDateTime) {
  return date.toDate(getLocalTimeZone()).toLocaleString();
}
</script>

<template>
  <Popover>
    <PopoverTrigger>
      <Button variant="outline">
        <CalendarIcon class="size-4" />
        {{ format(date) }}
      </Button>
    </PopoverTrigger>
    <PopoverContent class="p-2 grid gap-4">
      <Calendar v-model="date" class="mx-auto p-0" />
      <TimeField v-model="date" granularity="minute" :hour-cycle="12" v-slot="{ hour, minute, dayPeriod }" class="mx-auto dark:border-accent dark:bg-background/50">
        <Clock10Icon class="size-4" />

        <TimeFieldInput :segment="hour" />
        <TimeFieldSeparator />
        <TimeFieldInput :segment="minute" />
        <TimeFieldInput :segment="dayPeriod" />
      </TimeField>
    </PopoverContent>
  </Popover>
</template>
    

Hour Cycle

      
      <script setup lang="ts">
import { CalendarDateTime } from "@internationalized/date";
import { TimeField, TimeFieldInput, TimeFieldSeparator } from "@/components/time-field";
import { Clock10Icon } from "lucide-vue-next";
import { ref } from "vue";

const date = ref(new CalendarDateTime(2026, 3, 16, 16, 30, 0));
</script>

<template>
  <div class="flex flex-col gap-4">
    
    <TimeField v-model="date" granularity="minute" v-slot="{ hour, minute, dayPeriod }" class="w-fit">
      <Clock10Icon class="size-4" />
      
      <TimeFieldInput :segment="hour" />
      <TimeFieldSeparator />
      <TimeFieldInput :segment="minute" />
      <TimeFieldInput :segment="dayPeriod" />
    </TimeField>

    <TimeField v-model="date" granularity="minute" :hour-cycle="24" v-slot="{ hour, minute }" class="w-fit">
      <Clock10Icon class="size-4" />
      
      <TimeFieldInput :segment="hour" />
      <TimeFieldSeparator />
      <TimeFieldInput :segment="minute" />
    </TimeField>
  </div>
</template>