diff --git a/packages/vuetify/src/iconsets/mdi.ts b/packages/vuetify/src/iconsets/mdi.ts
index d1a9e788062..3392250c769 100644
--- a/packages/vuetify/src/iconsets/mdi.ts
+++ b/packages/vuetify/src/iconsets/mdi.ts
@@ -23,6 +23,7 @@ const aliases: IconAliases = {
checkboxOn: 'mdi-checkbox-marked',
checkboxOff: 'mdi-checkbox-blank-outline',
checkboxIndeterminate: 'mdi-minus-box',
+ clock: 'mdi-clock',
delimiter: 'mdi-circle', // for carousel
sortAsc: 'mdi-arrow-up',
sortDesc: 'mdi-arrow-down',
diff --git a/packages/vuetify/src/labs/VTimeInput/VTimeInput.tsx b/packages/vuetify/src/labs/VTimeInput/VTimeInput.tsx
new file mode 100644
index 00000000000..96b289a8556
--- /dev/null
+++ b/packages/vuetify/src/labs/VTimeInput/VTimeInput.tsx
@@ -0,0 +1,146 @@
+// Components
+import { VMenu } from '@/components/VMenu/VMenu'
+import { makeVTextFieldProps, VTextField } from '@/components/VTextField/VTextField'
+import { makeVConfirmEditProps, VConfirmEdit } from '@/labs/VConfirmEdit/VConfirmEdit'
+import { makeVTimePickerProps, VTimePicker } from '@/labs/VTimePicker/VTimePicker'
+
+// Composables
+import { useDate } from '@/composables/date'
+import { makeFocusProps, useFocus } from '@/composables/focus'
+import { useLocale } from '@/composables/locale'
+import { useProxiedModel } from '@/composables/proxiedModel'
+
+// Utilities
+import { computed, shallowRef } from 'vue'
+import { genericComponent, omit, propsFactory, useRender, wrapInArray } from '@/util'
+
+// Types
+export interface VTimeInputSlots {
+ default: never
+}
+
+export const makeVTimeInputProps = propsFactory({
+ hideActions: Boolean,
+
+ ...makeFocusProps(),
+ ...makeVConfirmEditProps(),
+ ...makeVTextFieldProps({
+ placeholder: 'hh:mm',
+ prependIcon: '$clock',
+ }),
+ ...omit(makeVTimePickerProps({
+ weeksInMonth: 'dynamic' as const,
+ hideHeader: true,
+ }), ['active']),
+}, 'VTimeInput')
+
+export const VTimeInput = genericComponent()({
+ name: 'VTimeInput',
+
+ props: makeVTimeInputProps(),
+
+ emits: {
+ 'update:modelValue': (val: string) => true,
+ },
+
+ setup (props, { slots }) {
+ const { t } = useLocale()
+ const adapter = useDate()
+ const { isFocused, focus, blur } = useFocus(props)
+ const model = useProxiedModel(props, 'modelValue', props.multiple ? [] : null)
+ const menu = shallowRef(false)
+
+ const display = computed(() => {
+ const value = wrapInArray(model.value)
+
+ if (!value.length) return null
+
+ return adapter.isValid(model.value) ? adapter.format(model.value, 'keyboardDate') : ''
+ })
+
+ function onKeydown (e: KeyboardEvent) {
+ if (e.key !== 'Enter') return
+
+ if (!menu.value || !isFocused.value) {
+ menu.value = true
+
+ return
+ }
+
+ const target = e.target as HTMLInputElement
+
+ model.value = adapter.date(target.value)
+ }
+
+ function onClick (e: MouseEvent) {
+ e.preventDefault()
+ e.stopPropagation()
+
+ menu.value = true
+ }
+
+ function onSave () {
+ menu.value = false
+ }
+
+ useRender(() => {
+ const confirmEditProps = VConfirmEdit.filterProps(props)
+ const timePickerProps = VTimePicker.filterProps(omit(props, ['active']))
+ const textFieldProps = VTextField.filterProps(props)
+
+ return (
+
+
+
+ {{
+ default: ({ actions, model: proxyModel }) => {
+ return (
+ {
+ if (!props.hideActions) {
+ proxyModel.value = val
+ } else {
+ model.value = val
+ }
+ }}
+ onMousedown={ (e: MouseEvent) => e.preventDefault() }
+ >
+ {{
+ actions: !props.hideActions ? () => actions : undefined,
+ }}
+
+ )
+ },
+ }}
+
+
+
+ { slots.default?.() }
+
+ )
+ })
+ },
+})
+
+export type VTimeInput = InstanceType
diff --git a/packages/vuetify/src/labs/VTimeInput/index.ts b/packages/vuetify/src/labs/VTimeInput/index.ts
new file mode 100644
index 00000000000..7d898041ef2
--- /dev/null
+++ b/packages/vuetify/src/labs/VTimeInput/index.ts
@@ -0,0 +1 @@
+export { VTimeInput } from './VTimeInput'