Jordan Smalls

Software Engineer

Clip-path view transitions for theming

Reveal a theme swap with a clip-path wipe using the View Transitions API.

Target the new view snapshot on the root with ::view-transition-new(root) and animate its clip-path. A CSS variable holds the starting inset per theme, so the same keyframes can wipe from a different edge depending on which theme is being entered.

css
::view-transition-new(root) {
  animation: reveal 0.5s ease;
}

[data-theme='dark'] {
  --from: 0 0 100% 0;
}

[data-theme='light'] {
  --from: 100% 0 0 0;
}

@keyframes reveal {
  from {
    clip-path: inset(var(--from, 100% 0 0 0));
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

Trigger the swap through document.startViewTransition so the browser captures before/after snapshots to animate between. Toggling the theme attribute inside the callback is what makes the [data-theme] variable available for the keyframes above.

typescript
function toggleTheme() {
  const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'

  if (!document.startViewTransition) {
    document.documentElement.dataset.theme = next
    return
  }

  document.startViewTransition(() => {
    document.documentElement.dataset.theme = next
  })
}

inset(0 0 100% 0) means clipped fully from the top, so the reveal grows downward; the light theme uses the opposite edge to feel directional. Falls back to an instant swap in browsers without View Transitions support.

css
/* inset(top right bottom left) */
/* --from: 0 0 100% 0   -> wipe down from the top   */
/* --from: 100% 0 0 0   -> wipe up from the bottom   */