Skip to content

fix(VOverlay): promote to GPU compositing layer to prevent flicker on WebKit#22800

Open
e1berd wants to merge 4 commits into
vuetifyjs:masterfrom
e1berd:fix/v-overlay
Open

fix(VOverlay): promote to GPU compositing layer to prevent flicker on WebKit#22800
e1berd wants to merge 4 commits into
vuetifyjs:masterfrom
e1berd:fix/v-overlay

Conversation

@e1berd

@e1berd e1berd commented Apr 11, 2026

Copy link
Copy Markdown

Description

When any v-overlay-based component (v-dialog, v-menu, etc.) is open, rapidly moving the cursor causes the overlay to flicker — for a single frame (~16ms) the content disappears and the page behind the overlay becomes visible.

bug_proof.webm

Fixes the issue by adding transform: translateZ(0) to .v-overlay, which promotes it to a dedicated GPU compositing layer.

Root cause:

.v-overlay__scrim and .v-overlay__content are sibling elements with no GPU layer on their parent. During fast cursor movement, WebKit performs hit-testing on every mousemove event, which triggers compositing layer invalidation. Since neither child has its own GPU texture, the browser re-uploads them on each invalidation. During this re-upload, one child can be absent for exactly one frame — producing the visible flicker.

With transform: translateZ(0) on .v-overlay, both children share a single GPU texture. There is no inter-layer synchronization and no re-upload on cursor movement.

Why this doesn't break position: fixed children:

When a parent has transform, its position: fixed children are positioned relative to it instead of the viewport. This is safe here because .v-overlay itself is already position: fixed covering the full viewport (top: 0; right: 0; bottom: 0; left: 0), so .v-overlay__scrim still covers the full screen.

Affected environments: Linux (Tauri / WebKitGTK), potentially Safari on macOS/iOS. Chromium-based browsers are unaffected because their compositor promotes layers more aggressively by default.

Markup:

<template>
  <v-app>
    <v-dialog max-width="400">
      <template #activator="{ props }">
        <v-btn v-bind="props">Open dialog</v-btn>
      </template>
      <v-card
        title="Flicker test"
        text="Open this dialog on WebKit (Linux/Tauri or Safari) and move your cursor rapidly. Without the fix, the dialog flickers for one frame on fast cursor movement."
      />
    </v-dialog>
  </v-app>
</template>

My configuration where the issue is reproducible:

  • OS: CachyOS x86_64
  • CPU: Intel Core Ultra 9 185H (22 cores) @ 5.10 GHz
  • GPU: Intel Arc Graphics (integrated) @ 2.35 GHz
  • Laptop: Lenovo ThinkBook X IMH (model 21NW)

@J-Sek

J-Sek commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

I should be able to reproduce it with this basic example, right? What am I missing?

@J-Sek J-Sek added the S: needs reproduction The issue does not contain a valid reproduction label Apr 12, 2026
@e1berd

e1berd commented Apr 13, 2026

Copy link
Copy Markdown
Author

I should be able to reproduce it with this basic example, right? What am I missing?

indeed, my initial example turned out to be not quite working. It seems
the issue doesn't reproduce with simple modal dialogs. By making the
reproduction conditions more complex, the bug started to reproduce -
here is the example code from my project:

<template>
  <v-app>
    <v-main>
      <v-container>
        <v-dialog max-width="600">
          <template #activator="{ props }">
            <v-btn v-bind="props">Settings-like dialog</v-btn>
          </template>
          <v-card height="540" style="overflow: hidden">
            <div style="display: flex; height: 100%">
              <div
                style="
                  width: 180px;
                  flex-shrink: 0;
                  background: color-mix(in srgb, rgb(var(--v-theme-surface-variant)) 20%, transparent);
                "
              >
                <div class="pa-4 text-subtitle-2 font-weight-bold" style="opacity: 0.6">
                  Settings
                </div>
                <v-list density="compact" nav class="px-2">
                  <v-list-item
                    v-for="i in 5"
                    :key="i"
                    :title="`Section ${i}`"
                    :prepend-icon="'mdi-cog'"
                    rounded="lg"
                    :active="i === 1"
                    color="primary"
                  />
                </v-list>
              </div>

              <v-divider vertical />

              <div style="flex: 1; display: flex; flex-direction: column">
                <div class="d-flex align-center justify-space-between px-5 pt-4 pb-3">
                  <span class="text-subtitle-1 font-weight-medium">Section 1</span>
                </div>
                <v-divider />
                <div class="pa-4">
                  <v-text-field label="Username" variant="outlined" density="compact" class="mb-2" />
                  <v-textarea label="Bio" variant="outlined" density="compact" rows="3" />
                  <div style="display: flex; flex-wrap: wrap; gap: 8px" class="mt-2">
                    <button
                      v-for="color in colors"
                      :key="color"
                      class="swatch"
                      :style="{ backgroundColor: color }"
                    />
                  </div>
                </div>
              </div>
            </div>
          </v-card>
        </v-dialog>

        <v-dialog max-width="400" class="mt-4">
          <template #activator="{ props }">
            <v-btn v-bind="props" class="ml-4">Profile-like dialog</v-btn>
          </template>
          <v-card rounded="xl" style="overflow: hidden">
            <div
              style="
                height: 96px;
                background: linear-gradient(
                  135deg,
                  rgb(var(--v-theme-primary)),
                  color-mix(in srgb, rgb(var(--v-theme-primary)) 60%, rgb(var(--v-theme-secondary)))
                );
              "
            />
            <div style="padding-left: 16px; margin-top: -42px">
              <v-avatar size="84" color="primary" style="border: 4px solid rgb(var(--v-theme-surface))">
                <span class="text-h5 font-weight-medium">U</span>
              </v-avatar>
            </div>
            <v-card-text>
              <div class="text-h6 font-weight-bold">Username</div>
              <div class="text-body-2 text-on-surface-variant">user@email.com</div>
              <v-divider class="my-3" />
              <div class="text-body-2">Bio text goes here</div>
            </v-card-text>
            <v-divider />
            <v-card-actions class="pa-3">
              <v-btn block variant="tonal" color="primary">Action</v-btn>
            </v-card-actions>
          </v-card>
        </v-dialog>
      </v-container>
    </v-main>
  </v-app>
</template>

<script setup lang="ts">
const colors = [
  '#5c6bc0', '#1976d2', '#0288d1', '#00897b',
  '#388e3c', '#7b1fa2', '#e91e63', '#f57c00',
  '#d32f2f', '#546e7a', '#795548', '#607d8b',
]
</script>

<style>
.swatch {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  transition: transform 0.12s ease;
}
.swatch:hover {
  transform: scale(1.15);
}
</style>

And likewise, if you add:

.v-overlay {
  will-change: transform;
}

the bug gets fixed and everything appears to be working fine

@e1berd

e1berd commented Apr 16, 2026

Copy link
Copy Markdown
Author

i wanted add that I'm also experiencing the same flickering issue on these other selectors:

  • .v-btn__overlay
  • .v-list-item__overlay
  • .v-chip__overlay
  • .v-field__overlay

in my case i was able to completely solve the problem by using the following styles:

.v-overlay {
  will-change: transform;

  .v-btn__overlay,
  .v-list-item__overlay,
  .v-chip__overlay,
  .v-field__overlay {
    will-change: opacity;
  }
}

this approach works very well for me and eliminates the flicker on all mentioned elements

@J-Sek J-Sek added the platform specific The issue only occurs on a specific platform label Apr 16, 2026
@J-Sek

J-Sek commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Is there a difference if it is running in dev mode? If not, then I suspect specific hardware or host OS can also have an impact here.

Regardless, the suggested fix is about adding a simple hint for the rendering engine, so it won't hurt to add. Let's just make sure we cover as much as possible now, so you don't have to post new PR on a weekly basis.

@e1berd

e1berd commented Apr 18, 2026

Copy link
Copy Markdown
Author

Is there a difference if it is running in dev mode? If not, then I suspect specific hardware or host OS can also have an impact here.

Regardless, the suggested fix is about adding a simple hint for the rendering engine, so it won't hurt to add. Let's just make sure we cover as much as possible now, so you don't have to post new PR on a weekly basis.

issue is clearly visible on WebKitGTK (Tauri on Linux)
also bug reproduces regardless of the environment - it happens equally in both dev and production modes

@J-Sek J-Sek added this to the v4.x.x milestone Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform specific The issue only occurs on a specific platform S: needs reproduction The issue does not contain a valid reproduction

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants