Theme Toggle
A minimal light/dark mode toggle with animated sun and moon icons. Client component powered by next-themes.
| Prop | Type | Default |
|---|---|---|
| variant | "solid" | "outlined" | "ghost" | "minimal" | "minimal" |
| size | "sm" | "md" | "lg" | "sm" |
| color | "root" | "primary" | "secondary" | "accent" | "root" |
import { ThemeToggle } from "@/bonk/components";
// Default (minimal, sm, root)
<ThemeToggle />
// Customize styling
<ThemeToggle variant="ghost" size="md" color="primary" />- This is a client component. It can be dropped into server components like Footer without making the parent a client component.
- Wraps IconButton — variant, size, and color are forwarded directly.
- The sun/moon icons animate on toggle via a spin-in keyframe animation.
- Requires a ThemeProvider (from next-themes) to be present in the component tree.
src/components/ThemeToggle.tsx
"use client";
import { useState, type ComponentProps } from "react";
import { IconButton } from "../ui";
import { DarkModeIcon, LightModeIcon } from "../ui/icons";
import { useTheme } from "next-themes";
interface ThemeToggleProps {
variant?: ComponentProps<typeof IconButton>["variant"];
size?: ComponentProps<typeof IconButton>["size"];
color?: ComponentProps<typeof IconButton>["color"];
}
export function ThemeToggle({
variant = "minimal",
size = "sm",
color = "root",
}: ThemeToggleProps) {
const [animKey, setAnimKey] = useState(0);
const { resolvedTheme, setTheme } = useTheme();
return (
<IconButton
variant={variant}
size={size}
color={color}
onClick={() => {
setTheme(resolvedTheme === "dark" ? "light" : "dark");
setAnimKey((k) => k + 1);
}}
aria-label="Toggle theme"
>
<span
key={animKey}
className={`inline-flex${animKey > 0 ? " animate-spin-in" : ""}`}
>
<span className="block dark:hidden">
<LightModeIcon />
</span>
<span className="hidden dark:block">
<DarkModeIcon />
</span>
</span>
</IconButton>
);
}