actualbudget/actual · error
Unrecognised Link variant.
Error message
Unrecognised Link variant.
What it means
The common Link component is a discriminated union on `variant` ('button' | 'internal' | 'external' | 'text') and its switch throws on anything else. Since TypeScript should make the default branch unreachable for typed callers, this fires only when variant is undefined/invalid — typically from untyped JavaScript callers, an any-typed props spread, or data-driven code passing a string from config/API.
Source
Thrown at packages/desktop-client/src/components/common/Link.tsx:177
}
case 'external': {
const { variant: _, ...externalProps } = props;
return <ExternalLink {...externalProps} />;
}
case 'button': {
const { variant: _, ...buttonProps } = props;
return <ButtonLink {...buttonProps} />;
}
case 'text': {
const { variant: _, ...textProps } = props;
return <TextLink {...textProps} />;
}
default:
throw new Error(`Unrecognised Link variant.`);
}
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Pass a valid variant: one of 'button', 'internal', 'external', or 'text'.
- If the variant comes from data, validate/coerce it before rendering (whitelist check with a fallback like 'external').
- Fix the caller's typing so TS enforces the LinkProps union — avoid `as any`/any-typed spreads that hide the missing variant.
- If a no-variant default is desired, render a sensible fallback (e.g. external or text link) in the default branch instead of throwing.
Example fix
// before <Link to="https://example.com">Docs</Link> // no variant -> throws // after <Link variant="external" to="https://example.com">Docs</Link>
Defensive patterns
Strategy: type-guard
Validate before calling
const LINK_VARIANTS = ['button', 'internal', 'external', 'text'] as const;
const safeVariant = LINK_VARIANTS.includes(rawVariant as any) ? rawVariant : 'external';
<Link variant={safeVariant} to={to}>...</Link> Type guard
function isLinkVariant(v: unknown): v is 'button' | 'internal' | 'external' | 'text' {
return v === 'button' || v === 'internal' || v === 'external' || v === 'text';
} Try / catch
let link;
try {
link = <Link variant={variant as any} to={to}>{children}</Link>;
} catch (err) {
if (err instanceof Error && err.message.includes('Unrecognised Link variant')) {
link = <Link variant="external" to={to}>{children}</Link>;
} else {
throw err;
}
} Prevention
- Always pass an explicit `variant` prop to Link.
- Validate/coerce variant strings coming from config, CMS data, or any-typed sources with isLinkVariant before rendering.
- Avoid `as any` prop spreads that bypass the LinkProps discriminated union.
When it happens
Trigger: Rendering <Link> without a `variant` prop (variant === undefined), or passing a value outside 'button'|'internal'|'external'|'text' (e.g. 'url', 'anchor', a value read from JSON/props of type any), bypassing the LinkProps union type.
Common situations: Migrating from an older Link API that defaulted variant; mapping backend/config strings directly to <Link variant={someString}>; JS files (or .tsx files with @ts-strict-ignore) that let an invalid variant through type checking.
Related errors
- Unknown display type: ${String(type satisfies never)}
- Unhandled action type: ${action.type}
- Unknown item type:
- InitialFocus expects a single valid React element as its chi
- Unknown budget action type: ${String(type)}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/f3c52d11333ec921.
Report an issue: GitHub.