Skip to content
Magic UI SolidMagicUI Solid

Theme Toggler

Animated theme toggle using the View Transitions API with configurable clip-path shapes and origin.

@/demos/AnimatedThemeTogglerDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler />
</div>
)
}
Terminal window
pnpm dlx shadcn@latest add @magicui-solid/animated-theme-toggler
import { AnimatedThemeToggler } from '@/components/magicui/animated-theme-toggler'
<AnimatedThemeToggler />

Use variant to change the clip-path shape of the view transition (circle, square, triangle, diamond, rectangle, hexagon, or star):

<AnimatedThemeToggler variant="star" />

Expand from the viewport center instead of the button with fromCenter:

<AnimatedThemeToggler variant="hexagon" duration={600} fromCenter />

The preview at the top of this page uses the default circle reveal. Below, each variant has its own isolated preview so you can test one shape at a time.

@/demos/AnimatedThemeTogglerSquareDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerSquareDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler variant="square" />
</div>
)
}
@/demos/AnimatedThemeTogglerDiamondDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerDiamondDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler variant="diamond" />
</div>
)
}
@/demos/AnimatedThemeTogglerRectangleDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerRectangleDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler variant="rectangle" />
</div>
)
}
@/demos/AnimatedThemeTogglerHexagonDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerHexagonDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler variant="hexagon" />
</div>
)
}
@/demos/AnimatedThemeTogglerTriangleDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerTriangleDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler variant="triangle" />
</div>
)
}
@/demos/AnimatedThemeTogglerStarDemo.tsx
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerStarDemo() {
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler variant="star" />
</div>
)
}

Pass theme and onThemeChange to keep the parent theme state in sync on toggle. When controlled, the component skips localStorage and lets the parent own persistence.

@/demos/AnimatedThemeTogglerControlledDemo.tsx
import { createSignal, onCleanup, onMount } from 'solid-js'
import { AnimatedThemeToggler } from '@/registry/magicui/animated-theme-toggler'
export function AnimatedThemeTogglerControlledDemo() {
const [theme, setTheme] = createSignal<'light' | 'dark'>('dark')
onMount(() => {
const root = document.documentElement
const update = () => {
setTheme(root.dataset.theme === 'dark' ? 'dark' : 'light')
}
update()
const observer = new MutationObserver(update)
observer.observe(root, { attributes: true, attributeFilter: ['data-theme'] })
onCleanup(() => observer.disconnect())
})
return (
<div class="flex justify-center p-6">
<AnimatedThemeToggler
theme={theme()}
onThemeChange={(t) => {
document.documentElement.dataset.theme = t
localStorage.setItem('starlight-theme', t)
setTheme(t)
}}
/>
</div>
)
}

The component sets --magicui-theme-toggle-vt-duration and a data-magicui-theme-vt="active" flag on <html> only while a toggle is in flight, so the clip-path animation stays in sync with the view-transition group without affecting any other view transitions in your app.

Add the following CSS to your global stylesheet:

html[data-magicui-theme-vt="active"]::view-transition-group(root) {
animation-duration: var(--magicui-theme-toggle-vt-duration);
}
Prop Type Default Description
class string - Additional classes for the button
duration number 400 Duration of the theme transition animation in milliseconds
variant TransitionVariant "circle" Shape used for the view-transition clip-path reveal
fromCenter boolean false If true, the clip expands from the viewport center instead of the button
theme "light" / "dark" - Controlled theme value. When set, the parent owns persistence.
onThemeChange (theme: "light" / "dark") => void - Called on toggle. Pair with theme for controlled usage.

Export TransitionVariant from the same module when you need typed props or menus.

Original:

magicui.design/docs/components/animated-theme-toggler