diff --git a/packages/docs/src/examples/v-alert/prop-duration.vue b/packages/docs/src/examples/v-alert/prop-duration.vue
new file mode 100644
index 00000000000..4d7ee8f6d50
--- /dev/null
+++ b/packages/docs/src/examples/v-alert/prop-duration.vue
@@ -0,0 +1,25 @@
+
+
+
+ Show alert
+
+
+
+
+
+
+
diff --git a/packages/docs/src/pages/en/components/alerts.md b/packages/docs/src/pages/en/components/alerts.md
index 97701b717a5..7959420d393 100644
--- a/packages/docs/src/pages/en/components/alerts.md
+++ b/packages/docs/src/pages/en/components/alerts.md
@@ -131,6 +131,12 @@ The **closable** prop adds a [v-icon](/components/icons) on the far right, after
+#### Duration
+
+The **duration** prop causes the alert to automatically dismiss itself after the specified number of milliseconds. Set to `-1` (the default) to disable auto-dismiss. Combine with **closable** to give users both auto-dismiss and manual control.
+
+
+
The close icon automatically applies a default `aria-label` and is configurable by using the **close-label** prop or changing **close** value in your locale.
::: info
diff --git a/packages/vuetify/src/components/VAlert/VAlert.tsx b/packages/vuetify/src/components/VAlert/VAlert.tsx
index 42672ddc0e0..b30684c40d7 100644
--- a/packages/vuetify/src/components/VAlert/VAlert.tsx
+++ b/packages/vuetify/src/components/VAlert/VAlert.tsx
@@ -25,7 +25,7 @@ import { makeThemeProps, provideTheme } from '@/composables/theme'
import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant'
// Utilities
-import { toRef } from 'vue'
+import { onMounted, onScopeDispose, toRef, watch } from 'vue'
import { genericComponent, propsFactory } from '@/util'
// Types
@@ -57,6 +57,10 @@ export const makeVAlertProps = propsFactory({
type: String,
default: '$vuetify.close',
},
+ duration: {
+ type: [Number, String],
+ default: -1,
+ },
icon: {
type: [Boolean, String, Function, Object] as PropType,
default: null,
@@ -107,6 +111,28 @@ export const VAlert = genericComponent()({
setup (props, { emit, slots }) {
const isActive = useProxiedModel(props, 'modelValue')
+
+ let dismissTimer = -1
+ function clearDismissTimer () {
+ if (dismissTimer !== -1) {
+ window.clearTimeout(dismissTimer)
+ dismissTimer = -1
+ }
+ }
+ function scheduleDismiss () {
+ clearDismissTimer()
+ const ms = Number(props.duration)
+ if (ms > 0 && isActive.value) {
+ dismissTimer = window.setTimeout(() => {
+ isActive.value = false
+ dismissTimer = -1
+ }, ms)
+ }
+ }
+ onMounted(scheduleDismiss)
+ watch(() => [props.duration, isActive.value], scheduleDismiss)
+ onScopeDispose(clearDismissTimer)
+
const icon = toRef(() => {
if (props.icon === false) return undefined
if (!props.type) return props.icon
diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx
index 1c68df64dc6..bf59237c55b 100644
--- a/packages/vuetify/src/components/VList/VListItem.tsx
+++ b/packages/vuetify/src/components/VList/VListItem.tsx
@@ -263,6 +263,8 @@ export const VListItem = genericComponent()({
}
}
+ const clickHandler = computed(() => (isClickable.value || attrs.onClick) ? onClick : undefined)
+
useRender(() => {
const Tag = isLink.value ? 'a' : props.tag
const hasTitle = (slots.title || props.title != null)
@@ -317,7 +319,7 @@ export const VListItem = genericComponent()({
tabindex={ props.tabindex ?? (isClickable.value ? (list ? -2 : 0) : undefined) }
aria-selected={ ariaSelected.value }
role={ role.value }
- onClick={ onClick }
+ onClick={ clickHandler.value }
onKeydown={ isClickable.value && !isLink.value && onKeyDown }
v-ripple={ isClickable.value && rippleOptions.value }
>