Literal Types
Literal types restrict a value to a specific primitive — a particular string, number, boolean, or bigint.
1function acceptsTwo(value: 2) { /* ... */ }2
3acceptsTwo(2); // Works!4acceptsTwo(3); // Error!incompatible-typeCannot call acceptsTwo with 3 bound to value because number literal 3 [1] is incompatible with number literal 2 [2].Literal types support booleans (true, false), numbers (42, 3.14), strings ("foo"), and bigints (42n). They are commonly combined with union types:
1function getColor(name: "success" | "warning" | "danger") {2 switch (name) {3 case "success" : return "green";4 case "warning" : return "yellow";5 case "danger" : return "red";6 }7}8
9getColor("success"); // Works!10getColor("danger"); // Works!11
12getColor("error"); // Error!incompatible-typeCannot call getColor with "error" bound to name because string literal error [1] is incompatible with union type [2].When to use this
Use literal types when you need to restrict a value to specific constants rather than a broad type like string or number. They are especially useful in unions to define a fixed set of allowed values (e.g. "success" | "error"). When you want to preserve a literal value's narrow type inside an object or array — where properties widen by default — reach for as const; see Literal widening below. For a more structured alternative to unions of literals, consider Flow Enums.
Literal widening
A const binding preserves a primitive literal's narrow type; a let binding widens it to the primitive base, because let allows reassignment and the narrower type would no longer be sound:
1const c = "success";2c as "success"; // Works! — c has the literal type "success"3
4let l = "success";5l as "success"; // Error — l has been widened to stringincompatible-typeCannot cast l to string literal success because string [1] is incompatible with string literal success [2].Object and array literals widen further: each property widens to its primitive base, and array literals widen from a tuple to Array<base> — both even when the surrounding binding is const, because the contents are still mutable. Use as const to preserve the literal types, keep the tuple structure, and mark the contents read-only:
1const o = {x: 1}; // type is {x: number}2const oConst = {x: 1} as const; // type is {readonly x: 1}3
4oConst.x as 1; // Works!5o.x as 1; // Error — o.x is numberincompatible-typeCannot cast o.x to number literal 1 because number [1] is incompatible with number literal 1 [2].6
7const arr = ["a", "b"]; // type is Array<string>8const tuple = ["a", "b"] as const; // type is Readonly<["a", "b"]>9
10tuple[0] as "a"; // Works!11arr[0] as "a"; // Error — arr[0] is stringincompatible-typeCannot cast arr[0] to string literal a because string [1] is incompatible with string literal a [2].12tuple as ReadonlyArray<"a" | "b">; // Works — Readonly<["a", "b"]> is a subtypeBecause as const produces read-only properties, a value created with as const cannot flow into a parameter whose property is writable (object properties are invariant by default). Mark the consuming parameter readonly to accept it:
1const e = {kind: "click"} as const;2
3function setKind(o: {kind: "click"}) {}4setKind(e); // Error — `kind` is read-only on `e` but writable on the parameterincompatible-varianceCannot call setKind with e bound to o because property kind is read-only in const object literal [1] but writable in object type [2].5
6function readKind(o: {readonly kind: "click"}) {}7readKind(e); // Works!See Also
- Primitive Types — the base types (
number,string, etc.) that literals specialize - Unions — commonly used with literals to define finite sets of values
- Const Expressions —
as constfor preserving literal types past assignment, especially inside containers - Variance — why a
readonlyvalue can't flow into a writable slot - Flow Enums — a structured alternative to unions of literal types