---
title: "Time Range Field"
description: "Allows users to input a range of times within a designated field."
canonical_url: "https://vinicunca.dev/akar/components/time-range-field"
---
# Time Range Field

> Allows users to input a range of times within a designated field.

`````vue [index.vue]
<script setup lang="ts">
import { Label, TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar'
import './styles.css'
</script>

<template>
  <div class="akar-demo akar-demo-TimeRangeField">
    <div class="TimeFieldWrapper">
        <Label
          class="TimeFieldLabel"
          for="time-field"
        >Input label</Label>

        <TimeRangeFieldRoot
          id="time-field"
          v-slot="{ segments }"
          granularity="second"
          class="TimeField"
        >
          <template
            v-for="(item, index) in segments.start"
            :key="`${item.part}-${index}`"
          >
            <TimeRangeFieldInput
              v-if="item.part === 'literal'"
              :part="item.part"
              class="TimeFieldLiteral"
              type="start"
            >
              {{ item.value }}
            </TimeRangeFieldInput>
            <TimeRangeFieldInput
              v-else
              :part="item.part"
              class="TimeFieldSegment"
              type="start"
            >
              {{ item.value }}
            </TimeRangeFieldInput>
          </template>
          -
          <template
            v-for="(item, index) in segments.end"
            :key="`${item.part}-${index}`"
          >
            <TimeRangeFieldInput
              v-if="item.part === 'literal'"
              :part="item.part"
              class="TimeFieldLiteral"
              type="end"
            >
              {{ item.value }}
            </TimeRangeFieldInput>
            <TimeRangeFieldInput
              v-else
              :part="item.part"
              class="TimeFieldSegment"
              type="end"
            >
              {{ item.value }}
            </TimeRangeFieldInput>
          </template>
        </TimeRangeFieldRoot>
      </div>
  </div>
</template>
```

```css [styles.css]
@import '../_colors/black-alpha.css';
@import '../_colors/mauve.css';
@import '../_colors/grass.css';

@scope (.akar-demo-TimeRangeField) {
.TimeFieldWrapper {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.TimeFieldLabel {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--gray-9);
}

.TimeField {
  display: flex;
  padding: 0.5rem;
  align-items: center;
  border-radius: 0.25rem;
  text-align: center;
  background-color: #fff;
  user-select: none;
  color: var(--green-10);
  border: 1px solid var(--gray-9);
}

.TimeField::placeholder {
  color: var(--mauve-5);
}

.TimeField[data-invalid] {
  border: 1px solid var(--red-500);
}

.TimeFieldLiteral {
  padding: 0.25rem;
}

.TimeFieldSegment {
  padding: 0.25rem;
}

.TimeFieldSegment:hover{
  background-color: var(--grass-4);
}

.TimeFieldSegment:focus {
  background-color: var(--grass-2);
}

.TimeFieldSegment[aria-valuetext='Empty'] {
  color: var(--grass-6);
}
}
```

## Features

- Full keyboard navigation
- Can be controlled or uncontrolled
- Focus is fully managed
- Localization support
- Highly composable
- Accessible by default
- Supports various time formats
- Time range validation
- Configurable granularity

## Preface

The component depends on the [@internationalized/date](https://react-spectrum.adobe.com/internationalized/date/index.html) package, which solves a lot of the problems that come with working with dates and times in JavaScript.

We highly recommend reading through the documentation for the package to get a solid feel for how it works, and you'll need to install it in your project to use the time-related components.

## Installation

Install the date package.

```bash [pnpm]
pnpm add @internationalized/date
```

```bash [yarn]
yarn add @internationalized/date
```

```bash [npm]
npm install @internationalized/date
```

```bash [bun]
bun add @internationalized/date
```

Install the component from your command line.

```bash [pnpm]
pnpm add akar
```

```bash [yarn]
yarn add akar
```

```bash [npm]
npm install akar
```

```bash [bun]
bun add akar
```

## Anatomy

Import all parts and piece them together.

```vue
<script setup>
import {
  TimeRangeFieldInput,
  TimeRangeFieldRoot,
} from 'akar';
</script>

<template>
  <TimeRangeFieldRoot>
    <TimeRangeFieldInput
      part="hour"
      type="start"
    />
    <TimeRangeFieldInput
      part="minute"
      type="start"
    />
    <TimeRangeFieldInput
      part="hour"
      type="end"
    />
    <TimeRangeFieldInput
      part="minute"
      type="end"
    />
  </TimeRangeFieldRoot>
</template>
```

## API Reference

### Root

Contains all the parts of a time range field.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](/akar/guides/composition) guide for more details. |
| `defaultPlaceholder` |  | `TimeValue` | The default placeholder time |
| `defaultValue` |  | `TimeRange` | The default value for the field |
| `dir` |  | `"ltr" \| "rtl"` | The reading direction of the time field when applicable.<br><br>If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `disabled` | `false` | `boolean` | Whether or not the time field is disabled |
| `granularity` |  | `"hour" \| "minute" \| "second"` | The granularity to use for formatting times. Defaults to minute. The field will render segments for each part of the time up to and including the specified granularity |
| `hideTimeZone` |  | `boolean` | Whether or not to hide the time zone segment of the field |
| `hourCycle` |  | `12 \| 24` | The hour cycle used for formatting times. Defaults to the local preference |
| `id` |  | `string` | Id of the element |
| `isTimeUnavailable` |  | `Matcher` | A function that returns whether or not a time is unavailable |
| `locale` |  | `string` | The locale to use for formatting times |
| `maxValue` |  | `TimeValue` | The maximum time that can be selected |
| `minValue` |  | `TimeValue` | The minimum time that can be selected |
| `modelValue` |  | `TimeRange \| null` | The controlled checked state of the field. Can be bound as `v-model`. |
| `name` |  | `string` | The name of the field. Submitted with its owning form as part of a name/value pair. |
| `placeholder` |  | `TimeValue` | The placeholder time, which is used to determine what time to display when no time is selected. This updates as the user navigates the field |
| `readonly` | `false` | `boolean` | Whether or not the time field is readonly |
| `required` |  | `boolean` | When `true`, indicates that the user must set the value before the owning form can be submitted. |
| `step` |  | `DateStep` | The stepping interval for the time fields. Defaults to `1`. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `date: TimeRange` | Event handler called whenever the model value changes |
| `update:placeholder` | `date: TimeValue` | Event handler called whenever the placeholder value changes |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `modelValue` | `TimeRange` | The current time of the field |
| `segments` | `{ start: { part: SegmentPart; value: string; }[]; end: { part: SegmentPart; value: string; }[]; }` | The time field segment contents |
| `isInvalid` | `boolean` | Value if the input is invalid |

**Methods**

| Method | Type | Description |
| --- | --- | --- |
| `setFocusedElement` | `(el: HTMLElement) => void` | Helper to set the focused element inside the TimeRangeField |

[TimeRangeFieldRoot.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/TimeRangeField/TimeRangeFieldRoot.vue)

| Data attribute | Value |
| --- | --- |
| `[data-readonly]` | Present when readonly |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |

### Input

Contains the time field segments.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](/akar/guides/composition) guide for more details. |
| `part*` |  | `"day" \| "month" \| "year" \| "hour" \| "minute" \| "second" \| "dayPeriod" \| "literal" \| "timeZoneName"` | The part of the date to render |
| `type*` |  | `"start" \| "end"` | The type of field to render (start or end) |

[TimeRangeFieldInput.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/TimeRangeField/TimeRangeFieldInput.vue)

| Data attribute | Value |
| --- | --- |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-placeholder]` | Present when no value is set |

## Accessibility

### Keyboard Interactions

| Key | Description |
| --- | --- |
| <kbd>Tab</kbd> | When focus moves onto the time field, focuses the first segment. |
| <kbd>ArrowLeft</kbd> / <kbd>ArrowRight</kbd> | Navigates between the time field segments. |
| <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Increments/changes the value of the segment. |
| <kbd>0-9</kbd> | When the focus is on a numeric `TimeRangeFieldInput`, it types in the number and focuses the next segment if the next input would result in an invalid value. |
| <kbd>Backspace</kbd> | Deletes a digit from the focused numeric segments. |
| <kbd>A</kbd> / <kbd>P</kbd> | When the focus is on the day period, it sets it to AM or PM. |

## Usage Examples

### Basic Usage

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});
</script>

<template>
  <TimeRangeFieldRoot v-model="timeRange">
    <div class="flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
      />
      :
      <TimeRangeFieldInput
        part="minute"
        type="start"
      />
      <span class="mx-2">to
        <TimeRangeFieldInput
          part="hour"
          type="end"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="end"
        />
      </span>
    </div>
  </TimeRangeFieldRoot>
</template>
```

### Controlled Component

```vue
<script setup>
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: undefined,
  end: undefined
});

function handleTimeRangeChange(newRange) {
  console.log('Time range changed:', newRange);
  timeRange.value = newRange;
}
</script>

<template>
  <TimeRangeFieldRoot
    :model-value="timeRange"
    @update:model-value="handleTimeRangeChange"
  >
    <div class="flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
      />
      :
      <TimeRangeFieldInput
        part="minute"
        type="start"
      />
      <span class="mx-2">to
        <TimeRangeFieldInput
          part="hour"
          type="end"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="end"
        />
      </span>
    </div>
  </TimeRangeFieldRoot>
</template>
```

### With Validation

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

// Disable times before 8 AM and after 6 PM
function isTimeUnavailable(time) {
  const hour = time.hour;
  return hour < 8 || hour > 18;
}
</script>

<template>
  <TimeRangeFieldRoot
    v-model="timeRange"
    :is-time-unavailable="isTimeUnavailable"
  >
    <div class="flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
      />
      :
      <TimeRangeFieldInput
        part="minute"
        type="start"
      />
      <span class="mx-2">to
        <TimeRangeFieldInput
          part="hour"
          type="end"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="end"
        />
      </span>
    </div>
    <p
      v-if="timeRange.start && isTimeUnavailable(timeRange.start)"
      class="text-sm color-red-500 mt-1"
    >
      Start time is unavailable
    </p>
    <p
      v-if="timeRange.end && isTimeUnavailable(timeRange.end)"
      class="text-sm color-red-500 mt-1"
    >
      End time is unavailable
    </p>
  </TimeRangeFieldRoot>
</template>
```

### With Different Granularity

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0, 30),
  end: new Time(17, 30, 0)
});
</script>

<template>
  <div class="space-y-4">
    <!-- Hour and minute only -->
    <div>
      <label class="text-sm font-medium mb-2 block">Hour and Minute</label>
      <TimeRangeFieldRoot
        v-model="timeRange"
        granularity="minute"
      >
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="start"
          />
          <span class="mx-2">to
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>

    <!-- Hour only -->
    <div>
      <label class="text-sm font-medium mb-2 block">Hour Only</label>
      <TimeRangeFieldRoot
        v-model="timeRange"
        granularity="hour"
      >
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          <span class="mx-2">to
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>

    <!-- Hour, minute, and second -->
    <div>
      <label class="text-sm font-medium mb-2 block">Hour, Minute, and Second</label>
      <TimeRangeFieldRoot
        v-model="timeRange"
        granularity="second"
      >
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="second"
            type="start"
          />
          <span class="mx-2">to
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="second"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>
  </div>
</template>
```

### With Locale and Hour Cycle

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});
</script>

<template>
  <div class="space-y-4">
    <!-- 24-hour format (default) -->
    <div>
      <label class="text-sm font-medium mb-2 block">24-hour format</label>
      <TimeRangeFieldRoot
        v-model="timeRange"
        hour-cycle="h23"
      >
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="start"
          />
          <span class="mx-2">to
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>

    <!-- 12-hour format with AM/PM -->
    <div>
      <label class="text-sm font-medium mb-2 block">12-hour format</label>
      <TimeRangeFieldRoot
        v-model="timeRange"
        hour-cycle="h12"
      >
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="start"
          />
          <TimeRangeFieldInput
            part="dayPeriod"
            type="start"
          />
          <span class="mx-2">to
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="end"
            />
            <TimeRangeFieldInput
              part="dayPeriod"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>

    <!-- French locale -->
    <div>
      <label class="text-sm font-medium mb-2 block">French locale</label>
      <TimeRangeFieldRoot
        v-model="timeRange"
        locale="fr"
      >
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="start"
          />
          <span class="mx-2">à
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>
  </div>
</template>
```

### With Min and Max Values

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

// Restrict times between 8 AM and 6 PM
const minTime = new Time(8, 0);
const maxTime = new Time(18, 0);
</script>

<template>
  <TimeRangeFieldRoot
    v-model="timeRange"
    :min-value="minTime"
    :max-value="maxTime"
  >
    <div class="flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
      />
      :
      <TimeRangeFieldInput
        part="minute"
        type="start"
      />
      <span class="mx-2">to
        <TimeRangeFieldInput
          part="hour"
          type="end"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="end"
        />
      </span>
    </div>
    <p class="text-sm color-gray-500 mt-1">
      Business hours: 8:00 AM - 6:00 PM
    </p>
  </TimeRangeFieldRoot>
</template>
```

### With Step Increment

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

// Increment minutes by 15
const step = { minute: 15 };
</script>

<template>
  <TimeRangeFieldRoot
    v-model="timeRange"
    :step="step"
  >
    <div class="flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
      />
      :
      <TimeRangeFieldInput
        part="minute"
        type="start"
      />
      <span class="mx-2">to
        <TimeRangeFieldInput
          part="hour"
          type="end"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="end"
        />
      </span>
    </div>
    <p class="text-sm color-gray-500 mt-1">
      Minutes increment by 15
    </p>
  </TimeRangeFieldRoot>
</template>
```

### Disabled State

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

const isDisabled = ref(true);
</script>

<template>
  <div class="space-y-4">
    <div class="flex gap-2 items-center">
      <input
        id="disable"
        v-model="isDisabled"
        type="checkbox"
      >
      <label for="disable">Disable time range field</label>
    </div>

    <TimeRangeFieldRoot
      v-model="timeRange"
      :disabled="isDisabled"
    >
      <div class="flex gap-2 items-center">
        <TimeRangeFieldInput
          part="hour"
          type="start"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="start"
        />
        <span class="mx-2">to
          <TimeRangeFieldInput
            part="hour"
            type="end"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="end"
          />
        </span>
      </div>
    </TimeRangeFieldRoot>
  </div>
</template>
```

### Read-only State

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

const isReadonly = ref(true);
</script>

<template>
  <div class="space-y-4">
    <div class="flex gap-2 items-center">
      <input
        id="readonly"
        v-model="isReadonly"
        type="checkbox"
      >
      <label for="readonly">Make time range field read-only</label>
    </div>

    <TimeRangeFieldRoot
      v-model="timeRange"
      :readonly="isReadonly"
    >
      <div class="flex gap-2 items-center">
        <TimeRangeFieldInput
          part="hour"
          type="start"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="start"
        />
        <span class="mx-2">to
          <TimeRangeFieldInput
            part="hour"
            type="end"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="end"
          />
        </span>
      </div>
    </TimeRangeFieldRoot>
  </div>
</template>
```

### Advanced Keyboard Navigation

The TimeRangeField provides intuitive keyboard navigation for efficient time input. Users can seamlessly move between time segments, increment or decrement values, and type numbers directly.

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});
</script>

<template>
  <div class="space-y-4">
    <p class="text-sm color-gray-600">
      Try navigating with Tab, Arrow keys, and typing numbers. Focus moves automatically between segments.
    </p>
    <TimeRangeFieldRoot v-model="timeRange">
      <div class="flex gap-2 items-center">
        <TimeRangeFieldInput
          part="hour"
          type="start"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="start"
        />
        <span class="mx-2">to
          <TimeRangeFieldInput
            part="hour"
            type="end"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="end"
          />
        </span>
      </div>
    </TimeRangeFieldRoot>
    <div class="text-xs color-gray-500">
      <strong>Keyboard shortcuts:</strong> Tab to navigate, Arrow Up/Down to change values, type numbers to input.
    </div>
  </div>
</template>
```

### Form Integration

Integrate TimeRangeField with HTML forms to handle submissions and validation.

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

function handleSubmit() {
  console.log('Form submitted with time range:', timeRange.value);
  // Handle form submission
}
</script>

<template>
  <form
    class="space-y-4"
    @submit.prevent="handleSubmit"
  >
    <div>
      <label class="text-sm font-medium mb-2 block">Select Time Range</label>
      <TimeRangeFieldRoot v-model="timeRange">
        <div class="flex gap-2 items-center">
          <TimeRangeFieldInput
            part="hour"
            type="start"
          />
          :
          <TimeRangeFieldInput
            part="minute"
            type="start"
          />
          <span class="mx-2">to
            <TimeRangeFieldInput
              part="hour"
              type="end"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="end"
            />
          </span>
        </div>
      </TimeRangeFieldRoot>
    </div>
    <button
      type="submit"
      class="color-white px-4 py-2 rounded bg-blue-500"
    >
      Submit
    </button>
  </form>
</template>
```

### Custom Styling

Customize the appearance of the TimeRangeField using CSS classes or Tailwind utilities.

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});
</script>

<template>
  <TimeRangeFieldRoot v-model="timeRange">
    <div class="p-4 border border-gray-300 rounded-lg bg-gray-50 flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
        class="px-2 py-1 text-center border border-blue-300 rounded w-12 focus:outline-none focus:border-blue-500"
      />
      <span class="color-gray-500">:
        <TimeRangeFieldInput
          part="minute"
          type="start"
          class="px-2 py-1 text-center border border-blue-300 rounded w-12 focus:outline-none focus:border-blue-500"
        />
        <span class="color-gray-500 mx-2">to
          <TimeRangeFieldInput
            part="hour"
            type="end"
            class="px-2 py-1 text-center border border-blue-300 rounded w-12 focus:outline-none focus:border-blue-500"
          />
          <span class="color-gray-500">:
            <TimeRangeFieldInput
              part="minute"
              type="end"
              class="px-2 py-1 text-center border border-blue-300 rounded w-12 focus:outline-none focus:border-blue-500"
            />
          </span></span></span>
    </div>
  </TimeRangeFieldRoot>
</template>
```

### Advanced Validation

Implement complex validation rules, such as ensuring the end time is after the start time and within business hours.

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref, watch } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});

const errors = ref([]);

watch(timeRange, (newRange) => {
  errors.value = [];
  if (newRange.start && newRange.end) {
    if (newRange.start.compare(newRange.end) >= 0) {
      errors.value.push('End time must be after start time');
    }
    if (newRange.start.hour < 8 || newRange.start.hour > 18) {
      errors.value.push('Start time must be between 8 AM and 6 PM');
    }
    if (newRange.end.hour < 8 || newRange.end.hour > 18) {
      errors.value.push('End time must be between 8 AM and 6 PM');
    }
  }
}, { deep: true });
</script>

<template>
  <TimeRangeFieldRoot v-model="timeRange">
    <div class="flex gap-2 items-center">
      <TimeRangeFieldInput
        part="hour"
        type="start"
      />
      :
      <TimeRangeFieldInput
        part="minute"
        type="start"
      />
      <span class="mx-2">to
        <TimeRangeFieldInput
          part="hour"
          type="end"
        />
        :
        <TimeRangeFieldInput
          part="minute"
          type="end"
        />
      </span>
    </div>
    <div
      v-if="errors.length"
      class="mt-2"
    >
      <p
        v-for="error in errors"
        :key="error"
        class="text-sm color-red-500"
      >
        {{ error }}
      </p>
    </div>
  </TimeRangeFieldRoot>
</template>
```

### Accessibility Features

The TimeRangeField is built with accessibility in mind. Enhance it further with ARIA labels and descriptions for screen readers.

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const timeRange = ref({
  start: new Time(9, 0),
  end: new Time(17, 0)
});
</script>

<template>
  <div>
    <label
      for="time-range"
      class="text-sm font-medium mb-2 block"
    >Meeting Time Range</label>
    <TimeRangeFieldRoot
      id="time-range"
      v-model="timeRange"
      aria-describedby="time-range-help"
    >
      <div class="flex gap-2 items-center">
        <TimeRangeFieldInput
          part="hour"
          type="start"
          aria-label="Start hour"
        />
        <span aria-hidden="true">:
          <TimeRangeFieldInput
            part="minute"
            type="start"
            aria-label="Start minute"
          />
          <span
            class="mx-2"
            aria-hidden="true"
          >to
            <TimeRangeFieldInput
              part="hour"
              type="end"
              aria-label="End hour"
            />
            <span aria-hidden="true">:
              <TimeRangeFieldInput
                part="minute"
                type="end"
                aria-label="End minute"
              />
            </span></span></span>
      </div>
    </TimeRangeFieldRoot>
    <p
      id="time-range-help"
      class="text-xs color-gray-500 mt-1"
    >
      Select the start and end times for your meeting. Use Tab to navigate between fields.
    </p>
  </div>
</template>
```

### Real-world Use Cases

Use TimeRangeField in practical scenarios like scheduling appointments or booking resources.

```vue
<script setup>
import { Time } from '@internationalized/date';
import { TimeRangeFieldInput, TimeRangeFieldRoot } from 'akar';
import { ref } from 'vue';

const appointment = ref({
  date: new Date(),
  timeRange: {
    start: new Time(10, 0),
    end: new Time(11, 0)
  },
  title: ''
});

function bookAppointment() {
  console.log('Booking appointment:', appointment.value);
  // API call to book appointment
}
</script>

<template>
  <div class="mx-auto p-6 rounded-lg bg-white max-w-md shadow-lg">
    <h2 class="text-xl font-bold mb-4">
      Book an Appointment
    </h2>
    <form
      class="space-y-4"
      @submit.prevent="bookAppointment"
    >
      <div>
        <label class="text-sm font-medium mb-2 block">Appointment Title</label>
        <input
          v-model="appointment.title"
          type="text"
          class="px-3 py-2 border border-gray-300 rounded w-full"
          required
        >
      </div>
      <div>
        <label class="text-sm font-medium mb-2 block">Date</label>
        <input
          v-model="appointment.date"
          type="date"
          class="px-3 py-2 border border-gray-300 rounded w-full"
          required
        >
      </div>
      <div>
        <label class="text-sm font-medium mb-2 block">Time Range</label>
        <TimeRangeFieldRoot v-model="appointment.timeRange">
          <div class="flex gap-2 items-center">
            <TimeRangeFieldInput
              part="hour"
              type="start"
              class="px-2 py-1 text-center border border-gray-300 rounded w-12"
            />
            :
            <TimeRangeFieldInput
              part="minute"
              type="start"
              class="px-2 py-1 text-center border border-gray-300 rounded w-12"
            />
            <span class="mx-2">to
              <TimeRangeFieldInput
                part="hour"
                type="end"
                class="px-2 py-1 text-center border border-gray-300 rounded w-12"
              />
              :
              <TimeRangeFieldInput
                part="minute"
                type="end"
                class="px-2 py-1 text-center border border-gray-300 rounded w-12"
              />
            </span>
          </div>
        </TimeRangeFieldRoot>
      </div>
      <button
        type="submit"
        class="color-white px-4 py-2 rounded bg-green-500 w-full hover:bg-green-600"
      >
        Book Appointment
      </button>
    </form>
  </div>
</template>
```

---

- [GitHub](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/TimeRangeField)
- [npm](https://www.npmjs.com/package/akar)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
