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 linearGradient when `start` or `end` is provided but is not an array of exactly two finite numbers. These are UV coordinates in [0,1] space defining the gradient endpoints; `undefined` is allowed (optional), but any other non-tuple value is rejected.

Source

Thrown at packages/effects/src/linear-gradient.ts:112

const resolve = (p: LinearGradientParams): LinearGradientResolved => ({
	start: [...(p.start ?? DEFAULT_START)] as LinearGradientUvCoordinate,
	end: [...(p.end ?? DEFAULT_END)] as LinearGradientUvCoordinate,
	startColor: p.startColor ?? DEFAULT_START_COLOR,
	endColor: p.endColor ?? DEFAULT_END_COLOR,
});

const assertOptionalUvCoordinate = (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`);
	}
};

const validateLinearGradientParams = (params: LinearGradientParams): void => {
	assertEffectParamsObject(params, 'Linear gradient');
	assertOptionalUvCoordinate(params.start, 'start');
	assertOptionalUvCoordinate(params.end, 'end');
	assertOptionalColor(params.startColor, 'startColor');
	assertOptionalColor(params.endColor, 'endColor');
};

const VERTEX_SHADER = /* glsl */ `#version 300 es
in vec2 aPos;
in vec2 aUv;
out vec2 vUv;

void main() {
	vUv = aUv;

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Pass UV tuples in [0,1]: `start: [0, 0]`, `end: [1, 1]`.
  2. Convert pixel coords: `start: [px / width, py / height]`.
  3. Validate both entries are finite numbers before calling.
  4. Omit `start`/`end` to use the documented defaults.

Example fix

// before
linearGradient({ start: [0, 0], end: [frameWidth, frameHeight] });

// after
linearGradient({ start: [0, 0], end: [1, 1] });
Defensive patterns

Strategy: type-guard

Validate before calling

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

const start = isUvTuple(userStart) ? userStart : undefined;
linearGradient(start === undefined ? {} : { start });

Type guard

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

Prevention

When it happens

Trigger: Passing `start: [0]`, `start: [0,0,0]`, `start: {x,y}`, `start: ['0','1']`, `start: [0, NaN]`, or `start: [0, Infinity]`. The guard requires a 2-element array where both entries are finite numbers.

Common situations: Passing pixel coordinates instead of UV; passing a Vec2-like object instead of a tuple; numbers deserialized to strings from JSON; off-by-one in array slicing producing wrong length.

Related errors


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