Overview
Scrim renders backdrop layers for overlays like dialogs and popovers. It integrates with useStack to automatically render one backdrop per active overlay with proper z-index stacking and dismissal behavior.
Installation
import { Scrim } from '@vuetify/v0'
Scrim requires createStackPlugin to be installed at the app level.
Setup
Install the stack plugin in your app:
import { createApp } from 'vue'
import { createStackPlugin } from '@vuetify/v0'
import App from './App.vue'
const app = createApp(App)
app.use(createStackPlugin())
app.mount('#app')
Basic Usage
<script setup lang="ts">
import { Scrim } from '@vuetify/v0'
</script>
<template>
<!-- Add once to app root or layout -->
<Scrim class="fixed inset-0 bg-black/50" />
</template>
Scrim automatically renders one backdrop per active overlay. You only need one Scrim component in your entire app.
Styling
The Scrim component handles z-index automatically. Style the backdrop appearance:
<Scrim class="fixed inset-0 bg-black/30 backdrop-blur-sm" />
Custom Opacity Stacking
<Scrim class="fixed inset-0 bg-black/20" />
Multiple scrims naturally create opacity stacking:
- First overlay: 20% opacity
- Second overlay: 20% + 20% = 36% opacity
- Third overlay: 20% + 20% + 20% = 49% opacity
Transitions
Add enter/leave animations:
<Scrim
transition="fade"
class="fixed inset-0 bg-black/50"
/>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
Custom Transition
<Scrim
transition="scrim-slide"
class="fixed inset-0 bg-black/50"
/>
.scrim-slide-enter-active,
.scrim-slide-leave-active {
transition: all 0.3s ease;
}
.scrim-slide-enter-from,
.scrim-slide-leave-to {
opacity: 0;
transform: scale(0.95);
}
Teleport Control
By default, Scrim teleports to <body>. Disable or customize:
<!-- Disable teleport -->
<Scrim
:teleport="false"
class="fixed inset-0 bg-black/50"
/>
<!-- Custom teleport target -->
<Scrim
teleport-to="#app-root"
class="fixed inset-0 bg-black/50"
/>
Custom Element
Render as different element:
<Scrim
as="section"
class="fixed inset-0 bg-black/50"
/>
Blocking Overlays
Scrims automatically respect blocking state from overlays:
<Dialog.Root>
<Dialog.Activator>Open</Dialog.Activator>
<!-- Scrim won't dismiss this dialog -->
<Dialog.Content :blocking="true">
<Dialog.Title>Blocking Dialog</Dialog.Title>
<p>Must use explicit close button.</p>
<Dialog.Close>Close</Dialog.Close>
</Dialog.Content>
</Dialog.Root>
<Scrim class="fixed inset-0 bg-black/50" />
Slot Props
Access scrim layer information:
<Scrim v-slot="{ ticket, zIndex, isBlocking }" class="fixed inset-0">
<div
:style="{ opacity: isBlocking ? 0.8 : 0.5 }"
class="absolute inset-0 bg-black"
/>
<div class="absolute top-4 right-4 text-white text-xs">
Layer {{ ticket.index + 1 }} (z-index: {{ zIndex }})
</div>
</Scrim>
Component API
Props
as
string | Component
default:"'div'"
Element or component to render as.
Transition name for enter/leave animations.
Whether to teleport the scrim to the body element.
teleportTo
string | HTMLElement
default:"'body'"
Target selector or element for teleport.
Slot Props
interface ScrimSlotProps {
ticket: StackTicket
zIndex: number
isBlocking: boolean
dismiss: () => void
}
The stack ticket for this scrim layer.
Z-index for this scrim layer (one below the overlay).
Whether this ticket’s overlay blocks scrim dismissal.
Function to dismiss this overlay (respects blocking state).
How It Works
- Stack Integration: Scrim subscribes to
useStack() to get active overlays
- Auto-render: Renders one backdrop per
selectedItems in the stack
- Z-index: Each scrim is positioned at
overlay.zIndex - 1
- Dismissal: Clicking non-blocking scrims dismisses their overlay
- Transitions: Uses
<TransitionGroup> for smooth enter/leave
Multiple Overlays
With multiple overlays, Scrim automatically stacks:
<!-- First dialog opens -->
<Scrim /> <!-- Renders 1 scrim at z-index 1999 -->
<!-- Second dialog opens -->
<Scrim /> <!-- Renders 2 scrims at z-index 1999, 2009 -->
<!-- First dialog closes -->
<Scrim /> <!-- Renders 1 scrim at z-index 2009 -->
The cumulative visual effect of multiple semi-transparent scrims creates natural depth perception for nested overlays.
Accessibility
- Scrims are purely visual and don’t interfere with focus management
- Click handlers respect blocking state
- Z-index coordination ensures proper stacking order
- Overlays handle their own ARIA attributes and focus trapping
TypeScript
import type { ScrimProps, ScrimSlotProps } from '@vuetify/v0'