They type string-based APIs precisely: event names, CSS units, route parameters, i18n keys and more.
Template literal types use the same backtick syntax as JavaScript template strings, but at the type level:
type Greeting = `hello ${string}`; // any string starting with "hello "
type Px = `${number}px`; // "12px", "1.5px", ...Placeholders can hold string, number, bigint, boolean, literal types and unions. With unions they produce the cross product:
type Size = "sm" | "lg";
type Color = "red" | "blue";
type ClassName = `${Size}-${Color}`; // "sm-red" | "sm-blue" | "lg-red" | "lg-blue"Be careful: large unions multiply quickly, and the compiler limits a union to around 100,000 members.
The intrinsic helpers Uppercase, Lowercase, Capitalize and Uncapitalize transform literal types. Combined with mapped-type key remapping they generate APIs such as onClick/onChange handler names from a list of events.
Template literal types also parse strings in conditional types using infer:
type Param<S> = S extends `${string}:${infer P}/${infer Rest}`
? P | Param<`/${Rest}`>
: S extends `${string}:${infer P}` ? P : never;
// Param<"/users/:id/posts/:postId"> is "id" | "postId"That's how routers type "/users/:id/posts/:postId" into { id: string; postId: string }. A placeholder before a fixed delimiter matches as little as possible (up to the first delimiter), which makes left-to-right parsing predictable.
Use these where strings really are structured. A pattern like ` ${number}px gives callers autocomplete and catches "12pz", but a value typed string` (e.g. from user input) isn't assignable to it without a check — so keep runtime validation at the boundary.
Example
type Size = "sm" | "lg";
type Tone = "primary" | "danger";
type ButtonClass = `btn-${Size}-${Tone}`; // 4 combinations
type Events = "click" | "focus";
type Handlers = { [E in Events as `on${Capitalize<E>}`]: (e: E) => void };
type RouteParams<S extends string> =
S extends `${string}:${infer P}/${infer Rest}`
? { [K in P | keyof RouteParams<`/${Rest}`>]: string }
: S extends `${string}:${infer P}` ? { [K in P]: string } : {};
type Params = RouteParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }
const cls: ButtonClass = "btn-lg-danger";
const handlers: Handlers = { onClick: e => console.log(e), onFocus: e => console.log(e) };
const params: Params = { userId: "7", postId: "42" };
const width: `${number}px` = "12.5px";
// @ts-expect-error -- "12pz" doesn't match `${number}px`
const bad: `${number}px` = "12pz";
handlers.onClick("click");
console.log(cls, params.postId, width);Remember
- Backtick types build string literal types; unions expand to all combinations
Capitalize& co. transform literals; pair them with key remappinginferinside template types parses strings at the type level
Watch out for
- Creating huge unions from cross products of large unions, which slows the checker or exceeds its limits.