Popmotion/popmotion · error
'${color}' is not an animatable color. Use the equivalent co
Error message
'${color}' is not an animatable color. Use the equivalent color code instead. What it means
`mixColor` needs both endpoints to be parseable, animatable colors. `getColorType(from)` returned no recognized color type (e.g. an unsupported format or a plain string like a named keyword it cannot parse), so `invariant(!!fromColorType, notAnimatable(from))` throws for the `from` argument. The library only animates colors in formats it has parsers for (hex, rgba, hsla, etc.).
Source
Thrown at packages/popmotion/src/utils/mix-color.ts:26
// Demonstrated http://codepen.io/osublake/pen/xGVVaN
export const mixLinearColor = (from: number, to: number, v: number) => {
const fromExpo = from * from
const toExpo = to * to
return Math.sqrt(Math.max(0, v * (toExpo - fromExpo) + fromExpo))
}
const colorTypes = [hex, rgba, hsla]
const getColorType = (v: Color | string) =>
colorTypes.find((type) => type.test(v))
const notAnimatable = (color: Color | string) =>
`'${color}' is not an animatable color. Use the equivalent color code instead.`
export const mixColor = (from: Color | string, to: Color | string) => {
let fromColorType = getColorType(from)
let toColorType = getColorType(to)
invariant(!!fromColorType, notAnimatable(from))
invariant(!!toColorType, notAnimatable(to))
let fromColor = fromColorType.parse(from)
let toColor = toColorType.parse(to)
if (fromColorType === hsla) {
fromColor = hslaToRgba(fromColor)
fromColorType = rgba
}
if (toColorType === hsla) {
toColor = hslaToRgba(toColor)
toColorType = rgba
}
const blended = { ...fromColor }
return (v: number) => {View on GitHub (pinned to adf681efd8)
Solutions
- Convert the first color to a supported format (hex, rgb() or hsla()) before calling
- Expand named keywords manually, e.g. 'red' -> '#ff0000'
- Resolve CSS variables to concrete color strings before animating
- Check the string for typos (partial hex, missing parens)
Example fix
// before
mixColor('red', '#0000ff');
// after
mixColor('#ff0000', '#0000ff'); Defensive patterns
Strategy: validation
Validate before calling
const COLOR_RE = /^(#([0-9a-f]{3,8})|rgba?\([^)]+\)|hsla?\([^)]+\))$/i;
function isAnimatableColor(c) {
return typeof c === 'string' && COLOR_RE.test(c.trim());
}
if (!isAnimatableColor(from)) throw new Error(`Cannot animate color: ${from}`); Type guard
function isAnimatableColor(value: unknown): value is string {
return typeof value === 'string' &&
/^(#([0-9a-f]{3,8})|rgba?\([^)]+\)|hsla?\([^)]+\))$/i.test(value.trim());
} Prevention
- Normalize all colors to hex or rgba at the boundary (theme load, token parsing)
- Never pass CSS variables, named keywords, or color functions like oklch()/lab() directly
- Store design tokens as concrete color strings, not references
- Sanitize user-supplied colors with a regex check before animating
When it happens
Trigger: `mixColor('red', '#0000ff')` (unsupported color keyword as the first arg), `mixColor('transparent-ish', ...)`, `mixColor(var(--color), '#fff')` where the variable resolves to an unsupported format, or a typo'd hex like `mixColor('#ff00', '#ffffff')`.
Common situations: Passing CSS named colors or `color-mix()`/`lab()`/`oklch()` strings the parser doesn't support; reading colors from CSS variables at runtime; typos in hex strings; design tokens that aren't concrete color values.
Related errors
- Both input and output ranges must be the same length
- Array of easing functions must be of length `input.length -
AI-assisted analysis of Popmotion/popmotion@adf681efd8 (2026-09-02).
Data as JSON: /api/errors/40b6f6f9ba4958b0.
Report an issue: GitHub.