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

  1. Pass a valid variant: one of 'button', 'internal', 'external', or 'text'.
  2. If the variant comes from data, validate/coerce it before rendering (whitelist check with a fallback like 'external').
  3. Fix the caller's typing so TS enforces the LinkProps union — avoid `as any`/any-typed spreads that hide the missing variant.
  4. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/f3c52d11333ec921. Report an issue: GitHub.