TypeScript course menu
TypeScript Template Literal Types: String Patterns as Types
Learn TypeScript template literal types: building string types from literal unions, generating every combination, intrinsic string types Uppercase, Lowercase, Capitalize and Uncapitalize, typing event names and CSS values, and inferring parts of strings.
On this page
Template literal types use the same backtick syntax as JavaScript template strings, but at the type level. They build string literal types from other types, so you can describe string patterns such as "on-click", "margin-top" or "/users/42".
Combining literals#
type Size = "sm" | "md" | "lg";
type Colour = "red" | "blue";
type ButtonClass = `btn-${Size}`; // "btn-sm" | "btn-md" | "btn-lg"
type Variant = `${Colour}-${Size}`; // every combination: 6 strings
const cls: ButtonClass = "btn-md";
const variant: Variant = "blue-lg";
console.log(cls, variant);btn-md blue-lgWhen a placeholder contains a union, TypeScript generates every combination, so Variant has 2 × 3 = 6 members. Invalid strings are rejected:
type Size = "sm" | "md" | "lg";
type ButtonClass = `btn-${Size}`;
const cls: ButtonClass = "btn-xl";error TS2820: Type '"btn-xl"' is not assignable to type '"btn-sm" | "btn-md" | "btn-lg"'. Did you mean '"btn-lg"'?Patterns with string and number#
Placeholders can also be string or number, describing open-ended patterns:
type Pixels = `${number}px`;
type UserRoute = `/users/${number}`;
type DataAttribute = `data-${string}`;
const width: Pixels = "320px";
const route: UserRoute = "/users/42";
const attr: DataAttribute = "data-testid";
console.log(width, route, attr);320px /users/42 data-testidtype Pixels = `${number}px`;
const width: Pixels = "wide";error TS2322: Type '"wide"' is not assignable to type '`${number}px`'.Intrinsic string types#
TypeScript has four built-in types that change the case of string literal types:
type Event = "click" | "focus" | "submit";
type Handler = `on${Capitalize<Event>}`; // "onClick" | "onFocus" | "onSubmit"
const handlers: Record<Handler, () => string> = {
onClick: () => "clicked",
onFocus: () => "focused",
onSubmit: () => "submitted",
};
console.log(Object.keys(handlers));[ 'onClick', 'onFocus', 'onSubmit' ]With mapped types#
Template literal types are often used to rename keys in mapped types:
type ChangeEvents<T> = {
[K in keyof T as `${string & K}Changed`]: (newValue: T[K]) => void;
};
interface Profile {
name: string;
age: number;
}
const events: ChangeEvents<Profile> = {
nameChanged: (v) => console.log("name is now", v),
ageChanged: (v) => console.log("age is now", v + 0),
};
events.ageChanged(22);age is now 22Inferring parts of a string#
Combined with conditional types and infer, template literals can parse string types:
type RouteParam<T extends string> = T extends `${string}:${infer Param}` ? Param : never;
type P = RouteParam<"/courses/:courseId">; // "courseId"
function buildUrl<T extends string>(template: T, param: RouteParam<T>, value: string): string {
return template.replace(`:${param}`, value);
}
console.log(buildUrl("/courses/:courseId", "courseId", "typescript"));/courses/typescriptLibraries use this technique to type routes, SQL-like query builders and event names. In application code, the simpler forms earlier in this lesson are the ones you will use most.
Practice#
Practice exercise
Define type Side = "top" | "right" | "bottom" | "left" and use template literal types to create MarginProperty ("margin-top" and so on) and CamelMargin ("marginTop" and so on, using Capitalize). Build a Record<CamelMargin, string> with values like "8px" and print it.
Show solution
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
type CamelMargin = `margin${Capitalize<Side>}`;
const kebab: MarginProperty = "margin-top";
const margins: Record<CamelMargin, string> = {
marginTop: "8px",
marginRight: "16px",
marginBottom: "8px",
marginLeft: "16px",
};
console.log(kebab, margins);margin-top {
marginTop: '8px',
marginRight: '16px',
marginBottom: '8px',
marginLeft: '16px'
}Key takeaway#
Finished this lesson?