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 linearGradientTint 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 (the param is optional), but any other non-tuple value is rejected.

Source

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

const resolve = (p: LinearGradientTintParams): LinearGradientTintResolved => ({
	start: [...(p.start ?? DEFAULT_START)] as LinearGradientTintUvCoordinate,
	end: [...(p.end ?? DEFAULT_END)] as LinearGradientTintUvCoordinate,
	startColor: p.startColor ?? DEFAULT_START_COLOR,
	endColor: p.endColor ?? DEFAULT_END_COLOR,
	amount: p.amount ?? DEFAULT_AMOUNT,
});

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 validateLinearGradientTintParams = (
	params: LinearGradientTintParams,
): void => {
	assertEffectParamsObject(params, 'Linear gradient tint');
	assertOptionalUvCoordinate(params.start, 'start');
	assertOptionalUvCoordinate(params.end, 'end');
	assertOptionalColor(params.startColor, 'startColor');
	assertOptionalColor(params.endColor, 'endColor');
	assertOptionalFiniteNumber(params.amount, 'amount');
	validateUnitInterval(resolve(params).amount, 'amount');
};

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

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Pass UV (0..1) tuples: `start: [0, 0.5]`, `end: [1, 0.5]`.
  2. Convert pixel coords: `start: [px / width, py / height]`.
  3. Validate before calling: ensure both elements are numbers and finite.
  4. Omit `start`/`end` to accept the documented defaults.

Example fix

// before
linearGradientTint({ start: [x, y], end: [w, h] }); // pixel space

// after
linearGradientTint({ start: [x / w, y / h], end: [1, 0.5] });
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;
linearGradientTint(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.5]` (length 1), `start: [0, 0, 0]` (length 3), `start: {x:0,y:0}`, `start: ['0','1']` (strings), `start: [0, NaN]`, or `start: [0, Infinity]`. The guard requires a 2-element array where both entries are finite numbers.

Common situations: Mixing up pixel coordinates and UV coordinates; passing a Vec2-like object instead of a tuple; deserializing from JSON where numbers became strings; using `[width/2, height/2]` (pixel-space) instead of `[0.5, 0.5]`.

Related errors


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