Prerequisites
- Understanding of basic TypeScript types ๐
- Knowledge of union types โก
- Familiarity with const assertions ๐ป
What you'll learn
- Create precise literal types ๐ฏ
- Combine literals with unions ๐๏ธ
- Use template literal types ๐
- Build type-safe APIs with literals โจ
๐ฏ Introduction
Welcome to the precise world of literal types in TypeScript! ๐ In this guide, weโll explore how to use exact values as types, creating incredibly specific and type-safe APIs.
Think of literal types as the difference between saying โany numberโ and โexactly 42โ ๐ฒ. Instead of accepting any string, you can demand exactly โsuccessโ or โerrorโ. Itโs like having a bouncer at a club who only lets in people with specific names on the guest list! ๐
By the end of this tutorial, youโll be creating APIs so precise that bugs will have nowhere to hide! Letโs dive in! ๐โโ๏ธ
๐ Understanding Literal Types
๐ค What Are Literal Types?
Literal types are like VIP passes ๐ซ - they represent exact, specific values rather than broad categories. Instead of string
, you can have "hello"
. Instead of number
, you can have 42
.
// Regular types (broad)
let status: string = "anything goes";
let count: number = 999;
// Literal types (specific)
let status: "success" = "success"; // Only "success" allowed!
let count: 42 = 42; // Only 42 allowed!
๐ก Types of Literals
TypeScript supports several literal types:
- String Literals ๐: Exact string values
- Number Literals ๐ข: Exact numeric values
- Boolean Literals โ
:
true
orfalse
(yes, theyโre literals!) - Template Literals ๐จ: String patterns (advanced)
๐ฏ Why Use Literal Types?
Hereโs why literal types are game-changers:
- Precise APIs ๐ฏ: Accept only specific values
- Better IntelliSense ๐ป: IDE knows exact options
- Compile-Time Safety ๐ก๏ธ: Catch typos immediately
- Self-Documenting ๐: Code shows exact values allowed
- Discriminated Unions ๐: Pattern matching with literals
Real-world example: HTTP methods ๐ - you want โGETโ, โPOSTโ, โPUTโ, not any random string!
๐ง Basic Literal Types
๐ String Literals
Letโs start with string literals - the most common type:
// ๐ฏ Simple string literal
type Direction = "north" | "south" | "east" | "west";
function move(direction: Direction) {
console.log(`Moving ${direction}! ๐งญ`);
}
move("north"); // โ
Works!
move("south"); // โ
Works!
// move("up"); // โ Error! "up" is not assignable to Direction
// ๐จ Real-world example: Status codes
type Status = "pending" | "approved" | "rejected";
interface Order {
id: number;
status: Status;
}
const order: Order = {
id: 123,
status: "approved" // โ
IntelliSense shows all options!
};
// ๐ Configuration options
type LogLevel = "debug" | "info" | "warn" | "error";
function log(message: string, level: LogLevel) {
const icons = {
debug: "๐",
info: "โน๏ธ",
warn: "โ ๏ธ",
error: "โ"
};
console.log(`${icons[level]} [${level.toUpperCase()}] ${message}`);
}
log("Application started", "info");
log("Something went wrong", "error");
๐ข Number Literals
Number literals for exact numeric values:
// ๐ฒ Dice rolls
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceRoll {
return Math.floor(Math.random() * 6) + 1 as DiceRoll;
}
// ๐ HTTP status codes
type SuccessCode = 200 | 201 | 204;
type ErrorCode = 400 | 404 | 500;
type StatusCode = SuccessCode | ErrorCode;
function handleResponse(code: StatusCode) {
switch (code) {
case 200:
console.log("โ
OK");
break;
case 404:
console.log("โ Not Found");
break;
// TypeScript ensures all cases handled!
}
}
// ๐ฎ Game difficulty levels
type Difficulty = 1 | 2 | 3;
interface GameSettings {
difficulty: Difficulty;
playerName: string;
}
const settings: GameSettings = {
difficulty: 2, // Medium
playerName: "Player1"
};
โ Boolean Literals
Even booleans can be literals:
// ๐ฑ Feature flags
type FeatureEnabled = true;
type FeatureDisabled = false;
interface Features {
darkMode: boolean; // Can be true or false
betaFeature: FeatureEnabled; // Must be true
deprecatedAPI: FeatureDisabled; // Must be false
}
// ๐ Strict configurations
type StrictMode = true; // Always strict!
interface CompilerOptions {
strict: StrictMode;
sourceMap: boolean;
}
const tsConfig: CompilerOptions = {
strict: true, // Must be true
sourceMap: false // Can be either
};
๐ Combining Literal Types
๐ฏ Union of Literals
The real power comes from combining literals:
// ๐จ Theme system
type ColorScheme = "light" | "dark" | "auto";
type ThemeColor = "blue" | "green" | "purple" | "orange";
interface Theme {
scheme: ColorScheme;
primaryColor: ThemeColor;
}
// ๐๏ธ Complex state machines
type LoadingState = {
status: "loading";
};
type SuccessState = {
status: "success";
data: string;
};
type ErrorState = {
status: "error";
message: string;
};
type State = LoadingState | SuccessState | ErrorState;
function handleState(state: State) {
switch (state.status) {
case "loading":
return "โณ Loading...";
case "success":
return `โ
${state.data}`;
case "error":
return `โ ${state.message}`;
// Exhaustive - all cases covered!
}
}
// ๐ฎ Game actions
type PlayerAction =
| { type: "move"; direction: Direction }
| { type: "attack"; target: string }
| { type: "defend" }
| { type: "useItem"; item: string };
function handleAction(action: PlayerAction) {
switch (action.type) {
case "move":
console.log(`Moving ${action.direction} ๐`);
break;
case "attack":
console.log(`Attacking ${action.target} โ๏ธ`);
break;
case "defend":
console.log("Defending ๐ก๏ธ");
break;
case "useItem":
console.log(`Using ${action.item} ๐งช`);
break;
}
}
๐ Literal Type Narrowing
TypeScript automatically narrows literal types:
// ๐ฏ Const assertions create literal types
const config = {
mode: "production",
port: 3000
} as const;
// Type is: { readonly mode: "production"; readonly port: 3000; }
// ๐ Array literals
const colors = ["red", "green", "blue"] as const;
// Type is: readonly ["red", "green", "blue"]
type Color = typeof colors[number]; // "red" | "green" | "blue"
// ๐จ Function that returns literals
function getStatus(success: boolean) {
return success ? "ok" : "error";
} // Return type: "ok" | "error"
// ๐๏ธ Building literal types from values
const ROLES = {
ADMIN: "admin",
USER: "user",
GUEST: "guest"
} as const;
type Role = typeof ROLES[keyof typeof ROLES]; // "admin" | "user" | "guest"
๐จ Template Literal Types
๐ String Pattern Types
Template literal types let you create string patterns:
// ๐ฏ Basic template literals
type EventName = `on${string}`;
const click: EventName = "onClick"; // โ
const change: EventName = "onChange"; // โ
// const invalid: EventName = "click"; // โ Must start with "on"
// ๐จ Combining literals
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";
type ColorShade = `${Shade}-${Color}`;
// Type is: "light-red" | "light-green" | "light-blue" |
// "dark-red" | "dark-green" | "dark-blue"
// ๐๏ธ CSS units
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px"; // โ
const height: CSSValue = "2.5rem"; // โ
// const invalid: CSSValue = "100"; // โ Missing unit
// ๐ URL patterns
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
type APIRoute = `${HTTPMethod} ${APIEndpoint}`;
const route1: APIRoute = "GET /api/users"; // โ
const route2: APIRoute = "POST /api/products"; // โ
// const invalid: APIRoute = "GET /users"; // โ Missing /api/
๐ Advanced Template Patterns
// ๐ฏ Uppercase/Lowercase transformations
type Greeting<T extends string> = `hello ${Lowercase<T>}`;
type Shout<T extends string> = `${Uppercase<T>}!`;
type HelloWorld = Greeting<"WORLD">; // "hello world"
type LoudHello = Shout<"hello">; // "HELLO!"
// ๐๏ธ Event handler names
type EventType = "click" | "change" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventType>}`;
// "onClick" | "onChange" | "onFocus" | "onBlur"
interface ButtonProps {
onClick?: () => void;
onFocus?: () => void;
// Autocomplete knows all valid handlers!
}
// ๐จ BEM CSS naming
type Block = "button" | "card" | "modal";
type Element = "header" | "body" | "footer";
type Modifier = "active" | "disabled" | "large";
type BEMClass =
| Block
| `${Block}__${Element}`
| `${Block}--${Modifier}`
| `${Block}__${Element}--${Modifier}`;
const class1: BEMClass = "button"; // โ
const class2: BEMClass = "card__header"; // โ
const class3: BEMClass = "modal--active"; // โ
const class4: BEMClass = "button__body--disabled"; // โ
๐ Best Practices and Patterns
๐ฏ Creating Type-Safe APIs
// ๐ API client with literal types
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint =
| "/users"
| "/products"
| "/orders"
| `/users/${string}`
| `/products/${string}`;
interface APIRequest {
method: Method;
endpoint: Endpoint;
body?: unknown;
}
class APIClient {
async request({ method, endpoint, body }: APIRequest) {
const options: RequestInit = {
method,
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined
};
return fetch(`https://api.example.com${endpoint}`, options);
}
}
// Usage with full type safety!
const client = new APIClient();
client.request({
method: "GET",
endpoint: "/users"
});
// ๐ฎ Game state machine
type GameState = "menu" | "playing" | "paused" | "gameOver";
type GameAction =
| { type: "START" }
| { type: "PAUSE" }
| { type: "RESUME" }
| { type: "END" };
const gameTransitions: Record<GameState, Partial<Record<GameAction["type"], GameState>>> = {
menu: { START: "playing" },
playing: { PAUSE: "paused", END: "gameOver" },
paused: { RESUME: "playing", END: "gameOver" },
gameOver: { START: "playing" }
};
function transition(state: GameState, action: GameAction): GameState {
return gameTransitions[state][action.type] ?? state;
}
๐ Real-World Example: Form Builder
// ๐๏ธ Type-safe form builder with literal types
type FieldType = "text" | "email" | "password" | "number" | "select" | "checkbox";
type ValidationRule = "required" | "email" | "minLength" | "maxLength" | "pattern";
interface FieldConfig<T extends FieldType> {
type: T;
name: string;
label: string;
placeholder?: string;
validation?: ValidationRule[];
options?: T extends "select" ? string[] : never;
min?: T extends "number" ? number : never;
max?: T extends "number" ? number : never;
}
// Type-safe field creators
function createTextField(config: FieldConfig<"text">) {
return { ...config, value: "" };
}
function createSelectField(config: FieldConfig<"select">) {
if (!config.options) {
throw new Error("Select field requires options!");
}
return { ...config, value: config.options[0] };
}
// Usage
const nameField = createTextField({
type: "text",
name: "fullName",
label: "Full Name",
validation: ["required", "minLength"]
});
const countryField = createSelectField({
type: "select",
name: "country",
label: "Country",
options: ["USA", "Canada", "UK", "Australia"], // Required for select!
validation: ["required"]
});
// ๐จ Form theme configuration
type ColorScheme = "light" | "dark";
type AccentColor = "blue" | "green" | "purple" | "red";
type Size = "sm" | "md" | "lg";
interface FormTheme {
scheme: ColorScheme;
accent: AccentColor;
inputSize: Size;
buttonSize: Size;
borderRadius: `${number}px`;
}
const theme: FormTheme = {
scheme: "light",
accent: "blue",
inputSize: "md",
buttonSize: "lg",
borderRadius: "4px"
};
๐ฏ Practice Exercise
Letโs build a type-safe state machine for a traffic light! ๐ฆ
// ๐ฆ Your challenge: Create a traffic light system
type LightColor = "red" | "yellow" | "green";
type LightState =
| { color: "red"; canGo: false }
| { color: "yellow"; canGo: false }
| { color: "green"; canGo: true };
// TODO: Implement the traffic light controller
class TrafficLight {
private state: LightState = { color: "red", canGo: false };
// TODO: Implement transition logic
next(): void {
// Red -> Green -> Yellow -> Red
}
// TODO: Get current state
getState(): LightState {
// Return current state
}
// TODO: Check if cars can go
canCarsGo(): boolean {
// Return based on state
}
}
// TODO: Create pedestrian crossing
type PedestrianSignal = "walk" | "dont-walk" | "flashing";
interface CrossingState {
traffic: LightColor;
pedestrian: PedestrianSignal;
}
// Map traffic lights to pedestrian signals
const crossingLogic: Record<LightColor, PedestrianSignal> = {
// TODO: Fill in the logic
};
// Test your implementation
const light = new TrafficLight();
console.log(light.getState()); // { color: "red", canGo: false }
light.next();
console.log(light.getState()); // { color: "green", canGo: true }
๐ Conclusion
Congratulations! Youโve mastered literal types in TypeScript! ๐ Letโs recap what youโve learned:
- ๐ String Literals: Exact string values as types
- ๐ข Number Literals: Precise numeric types
- โ Boolean Literals: Even true/false can be literal!
- ๐จ Template Literals: Pattern-based string types
Key takeaways:
- Use literals for precise, self-documenting APIs ๐ฏ
- Combine with unions for powerful type systems ๐
- Leverage template literals for string patterns ๐จ
- Let TypeScriptโs inference work for you ๐ค
Youโre now equipped to create incredibly type-safe APIs where bugs have nowhere to hide! Keep practicing with literal types - theyโre one of TypeScriptโs most powerful features for creating precise, user-friendly APIs! ๐