100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Template Literal Types in TypeScript

Build new string literal types by interpolating other types into a template, similar to JS template literals but evaluated at the type level.

Advanced TypesAdvanced13 min readJul 8, 2026
Analogies

1. Introduction

Template literal types, introduced in TypeScript 4.1, let you construct new string literal types using the same backtick syntax as JavaScript template literals, but with type placeholders instead of runtime expressions. Interpolating a union type inside a template literal type produces the cross-product union of all possible resulting strings, which makes them powerful for modeling patterns like CSS property names, event handler names, and structured string identifiers with full compile-time type safety.

🏏

Cricket analogy: Just as a fixture generator can cross the union of eight IPL teams with the union of home/away venues to compile every possible match-slot string ahead of the season, a template literal type crosses unions inside backticks to produce every valid string at compile time.

2. Syntax

typescript
type Direction = "top" | "right" | "bottom" | "left";
type Margin = `margin-${Direction}`;
// "margin-top" | "margin-right" | "margin-bottom" | "margin-left"

type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"

// intrinsic string manipulation types used with template literals
type Upper = Uppercase<"abc">;   // "ABC"
type Lower = Lowercase<"ABC">;   // "abc"
type Cap = Capitalize<"abc">;    // "Abc"
type Uncap = Uncapitalize<"Abc">; // "abc"

type Coordinate = `${number},${number}`;

3. Explanation

A template literal type is written with backticks and ${...} placeholders, exactly like a JS template literal, but each placeholder holds a type rather than a runtime expression — typically a string, number, boolean, bigint literal type, or a union of these. When a union is interpolated, TypeScript distributes across it (much like a distributive conditional type) and produces the union of every combination, so ` margin-${Direction} with a four-member union Direction` yields a four-member union of concrete margin strings.

🏏

Cricket analogy: Just as a scorecard template over-${BallNumber} with BallNumber as the union 1|2|3|4|5|6 distributes to produce six distinct over-position strings, margin-${Direction} with a four-member Direction union distributes to produce four concrete margin strings.

TypeScript ships four intrinsic string manipulation types — Uppercase<S>, Lowercase<S>, Capitalize<S>, Uncapitalize<S> — specifically to be composed with template literal types, since ordinary string methods only exist at runtime and have no type-level equivalent otherwise. Numeric placeholders (` ${number},${number} `) don't produce a union of every possible number; instead they widen to match any string that has the right lexical shape (digits in each position), which TypeScript checks structurally against literal string values.

🏏

Cricket analogy: A scorecard needs Uppercase<S> to render MSD from a batter's initials since string methods only run at runtime, while a numeric placeholder like over-${number}.${number} accepts any over-and-ball string of that shape, such as 12.4, without enumerating every possible over.

Template literal types are commonly combined with mapped types and key remapping (as) to derive new object shapes, e.g. { [K in keyof T as on${Capitalize<string & K>}]: (value: T[K]) => void } to auto-generate a matching set of event handler prop names from a props interface.

Gotcha: interpolating a large union inside a template literal type creates the full cross-product of possible strings, which can grow combinatorially — interpolating two independent 10-member unions produces up to 100 literal string types. TypeScript imposes a limit on the number of union members it will eagerly expand (historically capped around 100,000 combinations before erroring or falling back to a wider string type), so combining several large unions in one template can hit compiler limits or seriously slow down type-checking.

4. Example

typescript
type Direction = "top" | "right" | "bottom" | "left";
type Margin = `margin-${Direction}`;

const margins: Record<Margin, number> = {
  "margin-top": 4,
  "margin-right": 8,
  "margin-bottom": 4,
  "margin-left": 8,
};
console.log(Object.keys(margins).join(", "));

type EventName<T extends string> = `on${Capitalize<T>}`;

function on<T extends string>(event: EventName<T>, handler: () => void) {
  console.log(`registered ${event}`);
  handler();
}

on("onClick", () => console.log("clicked!"));
on("onHover", () => console.log("hovered!"));

type Coordinate = `${number},${number}`;
const point: Coordinate = "10,20";
console.log(point);

5. Output

text
margin-top, margin-right, margin-bottom, margin-left
registered onClick
clicked!
registered onHover
hovered!
10,20

6. Key Takeaways

  • Template literal types build new string literal types with backtick syntax and ${Type} placeholders.
  • Interpolating a union type distributes to produce the cross-product union of resulting strings.
  • Uppercase, Lowercase, Capitalize, and Uncapitalize are intrinsic types made for composing with template literal types.
  • Numeric/string placeholders like ${number} match structurally rather than enumerating every possible number.
  • Combining multiple large unions inside one template literal type can hit combinatorial compiler limits.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#TypeScriptProgrammingStudyNotes#Programming#TemplateLiteralTypesInTypeScript#Template#Literal#Types#Syntax#StudyNotes#SkillVeris