withastro/astro · error · Error

Invalid transition name {${transitionName}}

Error message

Invalid transition name {${transitionName}}

What it means

`renderTransition` handles `transition:name` rendering for View Transitions. The transition name must be a string (a CSS identifier is derived from it via `cssesc`). If `transitionName` (null-coalesced to `''`) is not of type `string` — e.g. a number, boolean, object, or array — Astro cannot build a valid CSS custom-identifier and throws.

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 d081033d5f)

Solutions

  1. Ensure the `transition:name` value is always a string: `transition:name={String(id)}`.
  2. Coerce numeric/computed values before binding them.
  3. Validate dynamic values when iterating lists that produce transition names.

Example fix

// before
<div transition:name={item.id} />   // item.id is a number -> throws

// after
<div transition:name={String(item.id)} />
Defensive patterns

Strategy: type-guard

Validate before calling

function assertTransitionName(name: unknown): asserts name is string {
  if (typeof (name ?? '') !== 'string') {
    throw new Error(`Invalid transition name {${String(name)}}`);
  }
}

Type guard

const isTransitionName = (v: unknown): v is string =>
  typeof (v ?? '') === 'string';

// In template:
transition:name={isTransitionName(name) ? name : String(name ?? '')}

Prevention

When it happens

Trigger: Passing `transition:name={123}` (number), a boolean, an object/array, or `undefined` that survives the `?? ''` coercion to a non-string type; an expression returning a non-string used as the transition name.

Common situations: Dynamic transition names computed from numeric IDs without string conversion; binding `transition:name` to a data field that is occasionally a number; spread-props carrying a non-string value.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/1b414851aff5157d. Report an issue: GitHub.