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.
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#
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));Move left 4 squarestype Direction = "up" | "down" | "left" | "right";
const d: Direction = "Left";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:
function setAlign(value: string) {} // any string, even "centre" or "middel"
function setAlign(value: "left" | "center" | "right") {} // exactly three valid optionsThe 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:
type Theme = "light" | "dark";
function applyTheme(theme: Theme) {}
const fixed = "dark"; // type "dark"
applyTheme(fixed); // OK
let changing = "dark"; // type string
applyTheme(changing);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:
type Method = "GET" | "POST";
function request(url: string, method: Method) {}
const options = { url: "/api/users", method: "GET" }; // method: string
request(options.url, options.method);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:
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));GET /api/usersDeriving 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:
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(", "));medium valid
huge invalid
small, medium, largetypeof SIZESis 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 Sizereturn 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
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(", "));viewer: false, editor: true, admin: trueconst ROLES = ["viewer", "editor", "admin"] as const;
type Role = (typeof ROLES)[number];
function canEdit(role: Role): boolean {
return role !== "viewer";
}
canEdit("owner");error TS2345: Argument of type '"owner"' is not assignable to parameter of type '"viewer" | "editor" | "admin"'.Key takeaway#
Finished this lesson?