# Vue 3 Time Picker [![Live Playground](https://img.shields.io/badge/Live-Playground-4f46e5?style=for-the-badge&logo=storybook&logoColor=white)](https://manos02.github.io/vue3-time-picker/) [![npm version](https://img.shields.io/npm/v/@manik02/vue3-timepicker?style=for-the-badge)](https://www.npmjs.com/package/@manik02/vue3-timepicker) [![License: MIT](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE) A Vue 3 time picker component with TypeScript support, multiple display formats, range selection, min/max constraints, disabled times, validation events, and full CSS variable theming. - Live docs: [Interactive Playground](https://manos02.github.io/vue3-time-picker/) - Package page: [npm package](https://www.npmjs.com/package/@manik02/vue3-timepicker) - Issues: [GitHub issues](https://github.com/manos02/vue3-time-picker/issues) If this project helps you, a [GitHub star](https://github.com/manos02/vue3-time-picker) is appriciated. | Demo | Default | Dark | | --- | --- | --- | | ![Demo](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/demo.gif) | ![Default](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-default.png) | ![Dark](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-dark.png) | ![All themes](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-all.png) ## Features - Single and range time selection - 24-hour, 12-hour, and `k`/`kk` 1-24 hour display formats - Optional seconds - Typing support with an overwrite-only masked input - Keyboard and screen-reader friendly (arrow-key navigation, ARIA attributes) - Step intervals for hours, minutes, and seconds - `minTime`, `maxTime`, `disabledTimes`, and callback-based disable rules - Validation and error events for form workflows - Size presets, width control, custom classes, and CSS variable theming - TypeScript types exported with the package ## Installation ```bash npm install @manik02/vue3-timepicker ``` This package expects Vue 3 as a peer dependency. ## Quick Start ```vue ``` ### Important Value Behavior - `format` only changes how the value is displayed and edited. - The bound `v-model` value is always normalized as `HH:mm:ss` when present. - In single mode, use `string | null | undefined`. - In range mode, use `[string, string] | null | undefined`. Example: with `format="HH:mm"`, the UI may show `14:30`, but `v-model` still contains `14:30:00`. ## Playground Run Storybook locally: ```bash npm run storybook ``` Build the static docs site: ```bash npm run build-storybook ``` The repo includes a GitHub Pages workflow for publishing Storybook from `.github/workflows/storybook.yml`. ## Examples Unless noted otherwise, the examples below assume this shared setup: ```vue ``` ### Basic Single Picker ```vue ``` ### 24-Hour Format with Seconds ```vue ``` ### 12-Hour Format ```vue ``` While the input is focused, press `a` or `p` to toggle AM/PM. ### Lowercase am/pm ```vue ``` ### `k` Format (1-24 Hours) ```vue ``` ### Start Empty and Clear Programmatically ```vue ``` ### Range Picker ```vue ``` ### Range Picker with 30-Minute Intervals ```vue ``` ### Typing-Only Input ```vue ``` ### Step Intervals ```vue ``` ### Working-Hours Bounds ```vue ``` If the user enters a value outside the allowed bounds, the component clamps it and emits `out-of-range` validation. ### Disable Specific Times and Ranges ```vue ``` ### Disable Times with a Callback ```vue ``` ### React to Validation State ```vue ``` ### Listen to Validation and Error Events ```vue ``` ### Range Validation Example ```vue ``` ### Form-Friendly Attributes ```vue ``` In range mode, the second input automatically uses `${id}-end` and `${name}-end`. ### Custom Input Class ```vue ``` ### Width Control ```vue ``` Width precedence for each input field: 1. `inputWidth` prop 2. `--vtp-input-width` CSS variable 3. Built-in width heuristic based on `format` and `placeholder` ### Size Presets ```vue ``` ### CSS Variables Theme Example ```vue ``` ### Rounded Minimal Theme Example ```vue ``` ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `modelValue` | `string \| [string, string] \| null` | `undefined` | Current value. In range mode use a two-item tuple. | | `format` | `TimeFormat` | `"HH:mm"` | Display and input format. | | `placeholder` | `string` | `"Select time"` | Placeholder text for empty input(s). | | `id` | `string` | `undefined` | Input id. In range mode the second input uses `${id}-end`. | | `name` | `string` | `undefined` | Input name. In range mode the second input uses `${name}-end`. | | `tabindex` | `number` | `0` | Tab index for input field(s). | | `autocomplete` | `string` | `"off"` | Native HTML autocomplete value. | | `inputClass` | `string \| string[] \| Record` | `undefined` | Extra class or classes applied to each input. | | `inputWidth` | `string \| number` | `undefined` | Explicit width for each input. Numeric values are treated as `px`. | | `minInputWidth` | `string \| number` | `undefined` | Minimum width for each input. Numeric values are treated as `px`. | | `maxInputWidth` | `string \| number` | `undefined` | Maximum width for each input. Numeric values are treated as `px`. | | `componentWidth` | `string \| number` | `undefined` | Width for the outer shell. Numeric values are treated as `px`. | | `range` | `boolean` | `false` | Enables two inputs for a time range. | | `disabled` | `boolean` | `false` | Disables typing and dropdown interaction. | | `hideDropdown` | `boolean` | `false` | Hides the column picker and keeps the input typing-only. | | `hourStep` | `number` | `1` | Hour interval in the dropdown. | | `minuteStep` | `number` | `1` | Minute interval in the dropdown. | | `secondStep` | `number` | `1` | Second interval in the dropdown. | | `minTime` | `string` | `undefined` | Minimum allowed time in `HH:mm` or `HH:mm:ss`. | | `maxTime` | `string` | `undefined` | Maximum allowed time in `HH:mm` or `HH:mm:ss`. | | `disabledTimes` | `(string \| [string, string])[]` | `undefined` | Disabled time points or ranges. | | `isTimeDisabled` | `(time: InternalFormat) => boolean` | `undefined` | Callback for custom disabled-time rules. Return `true` to block a time. | | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size preset mapped to CSS variables. | ### Autocomplete Notes - `autocomplete` is forwarded directly to the native `` element. - In range mode, both inputs receive the same `autocomplete` value. - Browser autofill behavior also depends on the surrounding form, `id`, and `name` attributes. ## Events | Event | Payload | Description | | --- | --- | --- | | `update:modelValue` | `string \| [string, string] \| null` | Emitted when the value changes. | | `update:validationState` | `"valid" \| "invalid" \| "out-of-range"` | Emitted whenever the aggregated validation state changes. | | `validate` | `{ target, state, reason?, value }` | Emitted after validation runs for one input. | | `error` | `{ code, message }` | Emitted when invalid or disabled input is encountered. | ### `validate` Payload ```ts { target: "first" | "second"; state: "valid" | "invalid" | "out-of-range"; reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED"; value: string | null; } ``` - `value` is always normalized to `HH:mm:ss` when present. - `target` is always included, even in single-input mode. ### Validation States - `valid`: the value is accepted. - `invalid`: the value is incomplete, malformed, or blocked by disable rules. - `out-of-range`: the value was outside `minTime`/`maxTime` and was clamped. ## Format Tokens | Token | Output | Description | | --- | --- | --- | | `HH` | `00`-`23` | 24-hour, zero-padded | | `H` | `0`-`23` | 24-hour | | `hh` | `01`-`12` | 12-hour, zero-padded | | `h` | `1`-`12` | 12-hour | | `kk` | `01`-`24` | 1-24 hour, zero-padded | | `k` | `1`-`24` | 1-24 hour | | `mm` | `00`-`59` | Minutes, zero-padded | | `m` | `0`-`59` | Minutes | | `ss` | `00`-`59` | Seconds, zero-padded | | `s` | `0`-`59` | Seconds | | `A` / `P` | `AM` / `PM` | Uppercase AM/PM | | `a` / `p` | `am` / `pm` | Lowercase am/pm | Examples: - `HH:mm` - `HH:mm:ss` - `hh:mm A` - `hh:mm:ss a` - `kk:mm` ## Keyboard Behavior - Typing is overwrite-only rather than free-form insertion. - The mask auto-inserts `:` separators. - In 12-hour mode, press `a` or `p` while focused to toggle AM/PM. - `Backspace` moves the cursor left without clearing the entire value. - `ArrowUp` / `ArrowDown` open the dropdown, then step the segment under the cursor (hours, minutes, seconds, or AM/PM), skipping disabled times. - `ArrowLeft` / `ArrowRight` move the cursor to choose which segment the arrows change. - `Enter` commits the typed value and closes the dropdown. - `Escape` closes the dropdown columns. - Focus stays on the input; dropdown options are not tab stops. ## Styling The component exposes CSS custom properties on `.timepicker-shell`, so you can theme it from any parent container. ```css .my-theme .timepicker-shell { --vtp-font-family: Inter, sans-serif; --vtp-font-size: 14px; --vtp-bg: #ffffff; --vtp-color: #111827; --vtp-border: #d1d5db; --vtp-border-radius: 8px; --vtp-padding: 0.5rem 0.75rem; --vtp-focus-border: #2563eb; --vtp-focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.18); --vtp-error-border: #ef4444; --vtp-error-ring: 0 0 0 3px rgba(239, 68, 68, 0.15); --vtp-component-width: auto; --vtp-input-width: 12ch; --vtp-input-min-width: 0; --vtp-input-max-width: none; --vtp-separator-color: #9ca3af; --vtp-dropdown-bg: #ffffff; --vtp-dropdown-border: #e5e7eb; --vtp-dropdown-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); --vtp-dropdown-radius: 8px; --vtp-dropdown-max-height: 240px; --vtp-option-padding: 0.375rem 0.75rem; --vtp-option-radius: 6px; --vtp-option-hover-bg: #f3f4f6; --vtp-option-active-bg: #dbeafe; --vtp-option-active-color: #1e40af; --vtp-option-active-weight: 600; --vtp-columns-gap: 0.5rem; } ``` Common styling variables: | Variable | Purpose | | --- | --- | | `--vtp-bg` | Input background | | `--vtp-color` | Input text color | | `--vtp-border` | Input border color | | `--vtp-focus-border` | Focused border color | | `--vtp-focus-ring` | Focus ring shadow | | `--vtp-dropdown-bg` | Dropdown background | | `--vtp-dropdown-border` | Dropdown border color | | `--vtp-option-hover-bg` | Hovered option background | | `--vtp-option-active-bg` | Active option background | | `--vtp-option-active-color` | Active option text color | | `--vtp-input-width` | Default input width | | `--vtp-component-width` | Outer shell width | ## TypeScript The package exports these useful types: ```ts import type { DisabledTimeInput, InternalFormat, TimeFormat, TimePickerProps, ValidationReason, ValidationState, } from "@manik02/vue3-timepicker"; ``` ## Development ```bash npm run dev npm run storybook npm run test npm run build ``` ## License MIT