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

  1. Coerce the value to a string: transition:name={String(product.id)}
  2. Fix the data source to produce strings (serialize at the data boundary in frontmatter)
  3. 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

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


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)