Skip to content
Laviyx Learn

Try

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.

Advanced6 min readUpdated
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#

TypeScript
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);
Output
btn-md blue-lg

When a placeholder contains a union, TypeScript generates every combination, so Variant has 2 × 3 = 6 members. Invalid strings are rejected:

TypeScript
type Size = "sm" | "md" | "lg";
type ButtonClass = `btn-${Size}`;
const cls: ButtonClass = "btn-xl";
Compiler error
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:

TypeScript
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);
Output
320px /users/42 data-testid
TypeScript
type Pixels = `${number}px`;
const width: Pixels = "wide";
Compiler error
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:

TypeExample result
Uppercase<"get">"GET"
Lowercase<"POST">"post"
Capitalize<"name">"Name"
Uncapitalize<"Name">"name"
TypeScript
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));
Output
[ 'onClick', 'onFocus', 'onSubmit' ]

With mapped types#

Template literal types are often used to rename keys in mapped types:

TypeScript
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);
Output
age is now 22

Inferring parts of a string#

Combined with conditional types and infer, template literals can parse string types:

TypeScript
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"));
Output
/courses/typescript

Libraries 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
TypeScript
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);
Output
margin-top {
  marginTop: '8px',
  marginRight: '16px',
  marginBottom: '8px',
  marginLeft: '16px'
}

Key takeaway#

Finished this lesson?