Quick Overview
A literal type accepts exactly one value, so `"GET"` is narrower than `string`. Template literal types compose them, letting `` `on${Capitalize<K>}` `` generate every handler name from an existing type.
TypeScript allows you to manipulate literal types using string template literal syntax directly in the type system.
Just as template literals construct strings at runtime, Template Literal Types construct string types at compile time:
type EventType = "click" | "hover" | "focus";
type Component = "button" | "input" | "card";
// Automatically generates 9 literal string combinations!
type DOMEventName = `${Component}:${EventType}`;
// Inferred as: "button:click" | "button:hover" | "input:click" | ...
const handleEvent = (event: DOMEventName) => {
console.log("Handling:", event);
};
handleEvent("button:click"); // Valid!
// handleEvent("header:click"); // Error: Type "header:click" is not assignable
Use template literal types to enforce API route paths or CSS spacing tokens:
type HTTPMethod = "GET" | "POST";
type APIVersion = "v1" | "v2";
type Endpoint = `/api/${APIVersion}/${string}`;
const validRoute: Endpoint = "/api/v1/users";
// const badRoute: Endpoint = "/v1/users"; // Error: Must start with /api/
TypeScript provides built-in intrinsic string manipulation type utilities:
Uppercase<StringType>Lowercase<StringType>Capitalize<StringType>Uncapitalize<StringType>type Action = "create" | "update";
type GetterName = `get${Capitalize<Action>}`; // "getCreate" | "getUpdate"
Use template literal types for design system tokens and API route validation. Next, let's explore Type Assertions and as unknown as T!
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.