Skip to content

Commit 204f77f

Browse files
committed
fix(webapp): normalize resolved theme colors to rgb for framer-motion
Tailwind v4's default palette is oklch, which framer-motion can't interpolate. useThemeColor now round-trips the resolved color through a 1x1 canvas to get an animatable rgb value.
1 parent ad35b34 commit 204f77f

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
import { useState } from "react";
22

33
/**
4-
* Resolve a theme CSS variable to its concrete color once on mount.
5-
* framer-motion can't interpolate `var()` strings, so animated colors must be
6-
* resolved to real values first. The fallback is used during SSR and should
7-
* match the default dark theme (see tailwind.css).
4+
* Normalize any CSS color (hex, oklch, hsl, ...) to rgb()/rgba() by rendering
5+
* it to a 1x1 canvas. framer-motion can only interpolate hex/rgb/hsl, while
6+
* Tailwind v4's default palette is oklch.
7+
*/
8+
function toRgb(color: string): string {
9+
const canvas = document.createElement("canvas");
10+
canvas.width = canvas.height = 1;
11+
const ctx = canvas.getContext("2d");
12+
if (!ctx) return color;
13+
ctx.fillStyle = color;
14+
ctx.fillRect(0, 0, 1, 1);
15+
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
16+
return a === 255 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a / 255})`;
17+
}
18+
19+
/**
20+
* Resolve a theme CSS variable to a concrete, animatable color once on mount.
21+
* framer-motion can't interpolate `var()` strings or oklch values, so animated
22+
* colors must be resolved and normalized first. The fallback is used during
23+
* SSR and should match the default dark theme (see tailwind.css).
824
*/
925
export function useThemeColor(variable: `--${string}`, fallback: string): string {
1026
const [color] = useState(() => {
1127
if (typeof document === "undefined") return fallback;
12-
return getComputedStyle(document.documentElement).getPropertyValue(variable).trim() || fallback;
28+
const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
29+
return value ? toRgb(value) : fallback;
1330
});
1431
return color;
1532
}

0 commit comments

Comments
 (0)