withastro/astro · error · Error
Invalid transition name
Error message
Invalid transition name {${transitionName}} What it means
Every element using transition:animate / transition:name goes through renderTransition, which generates the view-transition-name CSS for View Transitions. The name must be a string; null and undefined fall back to an auto-generated scope, but any other non-string value (number, boolean, object, symbol) throws this error. Strings that merely look odd (leading digits, spaces) are fine — Astro re-encodes them into valid identifiers.
Solutions
- Coerce the value to a string: transition:name={String(product.id)}
- Fix the data source to produce strings (serialize at the data boundary in frontmatter)
- If the element should not participate in view transitions, remove the transition:name / transition:animate directive
Example fix
<!-- before -->
<div transition:name={product.id} transition:animate="slide">
<!-- after -->
<div transition:name={String(product.id)} transition:animate="slide"> Defensive patterns
Strategy: type-guard
Validate before calling
---
const rawName = product.id; // number from the database
const name = isValidTransitionName(rawName) ? rawName : String(rawName);
---
<div transition:name={name}> Type guard
const isValidTransitionName = (v: unknown): v is string | null | undefined => v == null || typeof v === 'string';
Prevention
- Always derive transition names from strings — wrap numeric ids with String() at the source
- Type the variable you pass to transition:name as string in frontmatter
- Remember null/undefined are safe (auto-generated scope); everything non-string is not
When it happens
Trigger: <div transition:name={42}>; transition:name={product.id} where the id is a number from the database or Astro.params; transition:name={{ slug: 'hero' }} passing an object by mistake; a name variable sourced from CMS data typed as anything but string.
Common situations: Dynamic transition names built from numeric IDs used without String(); passing a props object instead of one of its fields; refactors changing a name's type from string to number.
Related errors
- maxDuration must be a number
- The view transitions client API was called during a server…
- [transitions]: all view transition animations, including…
- Unexpected template-exit instruction without a matching…
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/1b414851aff5157d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/runtime/server/transition.ts:95
codepoint < 0x80
? codepoint === 95
? '__'
: (reEncodeValidChars[codepoint] ?? '_' + codepoint.toString(16).padStart(2, '0'))
: String.fromCodePoint(codepoint);
}
}
// Digits and minus sign at the beginning of the string are special, so we simply prepend an underscore
return reEncodeInValidStart[result.codePointAt(0) ?? 0] ? '_' + result : result;
}
export function renderTransition(
result: SSRResult,
hash: string,
animationName: TransitionAnimationValue | undefined,
transitionName: string,
) {
if (typeof (transitionName ?? '') !== 'string') {
throw new Error(`Invalid transition name {${transitionName}}`);
}
// Default to `fade` (similar to `initial`, but snappier)
if (!animationName) animationName = 'fade';
const scope = createTransitionScope(result, hash);
const name = transitionName ? cssesc(reEncode(transitionName), { isIdentifier: true }) : scope;
const sheet = new ViewTransitionStyleSheet(scope, name);
const animations = getAnimations(animationName);
if (animations) {
addPairs(animations, sheet);
} else if (animationName === 'none') {
sheet.addFallback('old', 'animation: none; mix-blend-mode: normal;');
sheet.addModern('old', 'animation: none; opacity: 0; mix-blend-mode: normal;');
sheet.addAnimationRaw('new', 'animation: none; mix-blend-mode: normal;');
sheet.addModern('group', 'animation: none');
}
const css = escapeStyleText(sheet.toString());View on GitHub (pinned to 52e6c34790)