Featured image of post Creating Powerful String Types with TS Template Literals Featured image of post Creating Powerful String Types with TS Template Literals

Creating Powerful String Types with TS Template Literals

Learn how to use TypeScript Template Literal Types to dynamically combine string literal types, enforcing safe CSS class names and API endpoints.

Introduction

TypeScript offers an advanced type system that allows developers to model complex runtime constraints at compile time.

Among these features, Template Literal Types (introduced in TypeScript 4.1) allow you to define structured string constraints by combining string literals.

By applying JavaScript’s template literal syntax (`hello ${name}`) to type space, you can enforce safe CSS class name patterns, automate API route mappings, and construct typed design systems. This guide reviews the core patterns of Template Literal Types.


1. The Core Concept of Template Literal Types

Template Literal Types work by taking union types and interpolating them into string placeholders. The compiler automatically generates all permutations of the combined values.

A Basic Permutation Example

type Align = "left" | "center" | "right";
type Valign = "top" | "middle" | "bottom";

// Define combinations of horizontal and vertical positions
// This automatically creates a union type containing 9 string variations:
// "left-top" | "left-middle" | ... | "right-bottom"
type Position = `${Align}-${Valign}`;

// Compiles successfully
const pos1: Position = "left-top";

// Error: "center-center" does not match the generated union type
const pos2: Position = "center-center";

Historically, generating these combinations required declaring each variation manually. Template Literal Types let you define these patterns dynamically.


2. Practical Production Usecases

① Prefixing Dynamic Class Names in Design Systems

When building UI libraries, you can restrict component properties to valid BEM-styled CSS classes:

type Size = "sm" | "md" | "lg";
type Variant = "primary" | "secondary" | "danger";

// Restrict class name strings to patterns like "btn-sm-primary" or "btn-lg-danger"
type ButtonClassName = `btn-${Size}-${Variant}`;

function getButtonClass(size: Size, variant: Variant): ButtonClassName {
  return `btn-${size}-${variant}`;
}

// Compiles successfully
const className = getButtonClass("sm", "primary"); // btn-sm-primary

// Error: "btn-xl-primary" is not assignable to ButtonClassName
const invalidClass: ButtonClassName = "btn-xl-primary";

② Enforcing API Host Protocols

Ensure team members construct API endpoints using the correct protocols (e.g., http or https) and approved domain namespaces:

type Protocol = "http" | "https";
type Domain = "api.example.com" | "staging.example.com";

// Enforce endpoints that begin with an approved protocol and domain
type ApiEndpoint = `${Protocol}://${Domain}/${string}`;

// Compiles successfully
const endpoint1: ApiEndpoint = "https://api.example.com/users";

// Error: Protocol "ftp" is not allowed
const endpoint2: ApiEndpoint = "ftp://api.example.com/users";

The ${string} suffix allows any trailing URL path, while the domain prefix is validated.


3. String Manipulation Helpers

TypeScript provides built-in utility types to transform characters inside template literal types:

  • Uppercase<S>: Converts a string to uppercase.
  • Lowercase<S>: Converts a string to lowercase.
  • Capitalize<S>: Capitalizes the first letter of a string.
  • Uncapitalize<S>: Uncapitalizes the first letter of a string.

You can use these helpers to enforce camelCase naming conventions, such as getter and setter methods on data objects:

type UserFields = "name" | "email";

// Automatically generates method names: "getName" | "getEmail"
type GetterNames = `get${Capitalize<UserFields>}`;

const userGetters: Record<GetterNames, () => string> = {
  getName: () => "Takao",
  getEmail: () => "[email protected]"
};

Conclusion

Template Literal Types allow you to enforce naming conventions and string structures in your application.

  1. Combine multiple unions within placeholders to generate all valid permutations.
  2. Use the ${string} wildcard to enforce prefix or suffix patterns.
  3. Use built-in string utilities like Capitalize to automate camelCase definitions.

Start using template literals in your type space to catch syntax and naming errors at compile time.