Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Literal Types: Exact Values as Types

Learn TypeScript literal types: string, number and boolean literals, unions of literals for fixed sets of options, literal widening with let and const, as const for objects and arrays, deriving unions from arrays, and literal types in function parameters.

Intermediate6 min readUpdated
On this page

A literal type is a type that allows exactly one value: the type "left" only accepts the string "left". On their own they are not very useful, but in unions they describe fixed sets of options precisely, and they are one of TypeScript's most practical features.

String, number and boolean literals#

TypeScript
type Direction = "up" | "down" | "left" | "right";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
type Yes = true;

function move(direction: Direction, steps: DiceRoll): string {
  return `Move ${direction} ${steps} squares`;
}

console.log(move("left", 4));
Output
Move left 4 squares
TypeScript
type Direction = "up" | "down" | "left" | "right";
const d: Direction = "Left";
Compiler error
error TS2820: Type '"Left"' is not assignable to type 'Direction'. Did you mean '"left"'?

TypeScript catches the capital letter, and suggests the closest valid value.

Literal unions instead of plain strings#

Compare these two function signatures:

TypeScript
function setAlign(value: string) {}                            // any string, even "centre" or "middel"
function setAlign(value: "left" | "center" | "right") {}       // exactly three valid options

The second documents the options, enables auto-complete in editors, and turns typos into compile errors. Literal unions are the usual TypeScript replacement for "magic strings".

Widening: let versus const#

TypeScript infers literal types for const declarations, but widens them to the general type for let, because a let can change:

TypeScript
type Theme = "light" | "dark";
function applyTheme(theme: Theme) {}

const fixed = "dark";      // type "dark"
applyTheme(fixed);         // OK

let changing = "dark";     // type string
applyTheme(changing);
Compiler error
error TS2345: Argument of type 'string' is not assignable to parameter of type 'Theme'.

The fix is to annotate the variable: let changing: Theme = "dark";.

Object properties widen too#

Properties of an object literal are widened, because objects can be changed:

TypeScript
type Method = "GET" | "POST";
function request(url: string, method: Method) {}

const options = { url: "/api/users", method: "GET" };   // method: string
request(options.url, options.method);
Compiler error
error TS2345: Argument of type 'string' is not assignable to parameter of type 'Method'.

Fixes: annotate the object with a type that says method: Method, or use as const.

as const#

as const tells TypeScript to infer the narrowest possible type: literal types for every value, and readonly for every property and array:

TypeScript
type Method = "GET" | "POST";
function request(url: string, method: Method): string {
  return `${method} ${url}`;
}

const options = { url: "/api/users", method: "GET" } as const;
// { readonly url: "/api/users"; readonly method: "GET" }

console.log(request(options.url, options.method));
Output
GET /api/users

Deriving a union from an array#

A powerful pattern: keep the list of options as a real array (usable at runtime) and derive the type from it, so the two can never get out of sync:

TypeScript
const SIZES = ["small", "medium", "large"] as const;
type Size = (typeof SIZES)[number];     // "small" | "medium" | "large"

function isSize(value: string): value is Size {
  return (SIZES as readonly string[]).includes(value);
}

for (const input of ["medium", "huge"]) {
  console.log(input, isSize(input) ? "valid" : "invalid");
}
console.log(SIZES.join(", "));
Output
medium valid
huge invalid
small, medium, large
  • typeof SIZES is the type of the array: readonly ["small", "medium", "large"].
  • [number] gets the type of its items: the union of the three literals.
  • The value is Size return type is a type guard, explained in Type Guards.

The same idea works for objects of constants, as shown in Enums and keyof and typeof.

Template literal types#

Literal types can also be combined into patterns, such as `${Size}-${Colour}`. These are covered in Template Literal Types.

Practice#

Practice exercise

Define const ROLES = ["viewer", "editor", "admin"] as const, derive a Role type from it, and write canEdit(role: Role): boolean that returns true for editors and admins. Then show what happens when you call it with "owner".

Show solution
TypeScript
const ROLES = ["viewer", "editor", "admin"] as const;
type Role = (typeof ROLES)[number];

function canEdit(role: Role): boolean {
  return role === "editor" || role === "admin";
}

console.log(ROLES.map((r) => `${r}: ${canEdit(r)}`).join(", "));
Output
viewer: false, editor: true, admin: true
TypeScript
const ROLES = ["viewer", "editor", "admin"] as const;
type Role = (typeof ROLES)[number];
function canEdit(role: Role): boolean {
  return role !== "viewer";
}
canEdit("owner");
Compiler error
error TS2345: Argument of type '"owner"' is not assignable to parameter of type '"viewer" | "editor" | "admin"'.

Key takeaway#

Finished this lesson?