remotion-dev/remotion · error · TypeError

"${name}" must be a [number, number] tuple

Error message

"${name}" must be a [number, number] tuple

What it means

Thrown by the `rings()` effect when `center` is supplied but is not a `[number, number]` tuple of finite numbers. `center` is the ring origin in normalized UV coordinates (each component later constrained to [0,1] by `validateUnitInterval`). The validator requires a two-element array where every element is a finite number; anything else (wrong length, strings, `NaN`, objects) is rejected.

Source

Thrown at packages/effects/src/rings.ts:182

		);
	}

	for (let i = 0; i < colors.length; i++) {
		assertRequiredColor(colors[i], `colors[${i}]`);
	}
};

const assertOptionalCenter = (value: unknown, name: string): void => {
	if (value === undefined) {
		return;
	}

	if (
		!Array.isArray(value) ||
		value.length !== 2 ||
		value.some((item) => typeof item !== 'number' || !Number.isFinite(item))
	) {
		throw new TypeError(`"${name}" must be a [number, number] tuple`);
	}

	validateUnitInterval(value[0], `${name}[0]`);
	validateUnitInterval(value[1], `${name}[1]`);
};

const validateRingsParams = (params: RingsParams): void => {
	assertEffectParamsObject(params, 'Rings');
	validateColors(params.colors);
	assertOptionalCenter(params.center, 'center');
	assertOptionalFiniteNumber(params.thickness, 'thickness');
	assertOptionalFiniteNumber(params.gap, 'gap');
	assertOptionalFiniteNumber(params.offset, 'offset');
	assertOptionalBoolean(params.maskToSourceAlpha, 'maskToSourceAlpha');

	const thickness = params.thickness ?? DEFAULT_THICKNESS;
	const gap = params.gap ?? DEFAULT_GAP;
	validatePositive(thickness, 'thickness');

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Pass a two-element numeric tuple such as `center: [0.5, 0.5]`.
  2. Omit `center` to use the default `[0.5, 0.5]`.
  3. Convert point objects with `center: [point.x, point.y]` and ensure both are finite before passing.

Example fix

// before
rings({center: pointerPosition}); // {x: number, y: number}
// after
rings({center: [pointerPosition.x, pointerPosition.y]});
Defensive patterns

Strategy: validation

Validate before calling

const validateRingsCenter = (center: unknown): [number, number] | undefined => {
  if (center === undefined) return undefined;
  if (!Array.isArray(center) || center.length !== 2 ||
      center.some((c) => typeof c !== 'number' || !Number.isFinite(c))) {
    throw new TypeError('center must be a [number, number] tuple');
  }
  const [x, y] = center as [number, number];
  if (x < 0 || x > 1 || y < 0 || y > 1) {
    throw new TypeError('center components must be within [0, 1]');
  }
  return [x, y];
};

rings({center: validateRingsCenter(maybeCenter)});

Type guard

const isRingsCenter = (v: unknown): v is [number, number] =>
  Array.isArray(v) &&
  v.length === 2 &&
  v.every((c) => typeof c === 'number' && Number.isFinite(c));

Prevention

When it happens

Trigger: Call `rings({center: [0.5]})`, `rings({center: [0.5, 0.5, 0]})`, `rings({center: {x: 0.5, y: 0.5}})`, `rings({center: ['0.5', '0.5']})`, or `rings({center: [0.5, Number.NaN]})`. Each component is also range-checked, so `center: [1.5, 0.5]` will instead throw the `validateUnitInterval` `">= 0"` / `"<= 1"` error on `center[0]`.

Common situations: Passing an `{x, y}` point object from a drag handler instead of a tuple; reading center from JSON where numbers became strings; reusing a 3D `[x,y,z]` vector; off-by-one when slicing a longer array.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/696a701b5db83566. Report an issue: GitHub.