Template literal types combine literal types with string interpolation. Here's how to use them.
Basic Syntax#
1// Simple template literal type
2type Greeting = `Hello, ${string}!`;
3
4const g1: Greeting = 'Hello, World!'; // OK
5const g2: Greeting = 'Hello, Alice!'; // OK
6// const g3: Greeting = 'Hi, World!'; // Error
7
8// With union types
9type Color = 'red' | 'green' | 'blue';
10type Size = 'small' | 'medium' | 'large';
11
12type ColorSize = `${Color}-${Size}`;
13// 'red-small' | 'red-medium' | 'red-large' |
14// 'green-small' | 'green-medium' | 'green-large' |
15// 'blue-small' | 'blue-medium' | 'blue-large'
16
17// Multiple placeholders
18type Coordinate = `${number},${number}`;
19const coord: Coordinate = '10,20'; // OKEvent Naming#
1// Event handler type
2type EventName = 'click' | 'focus' | 'blur' | 'change';
3type HandlerName = `on${Capitalize<EventName>}`;
4// 'onClick' | 'onFocus' | 'onBlur' | 'onChange'
5
6// Prop types for events
7type EventHandlers = {
8 [K in EventName as `on${Capitalize<K>}`]: (event: Event) => void;
9};
10// {
11// onClick: (event: Event) => void;
12// onFocus: (event: Event) => void;
13// onBlur: (event: Event) => void;
14// onChange: (event: Event) => void;
15// }
16
17// Add event listener types
18type AddListener<T extends string> = `add${Capitalize<T>}Listener`;
19type RemoveListener<T extends string> = `remove${Capitalize<T>}Listener`;
20
21type ClickListeners = AddListener<'click'>; // 'addClickListener'String Manipulation Types#
1// Built-in string manipulation types
2type Upper = Uppercase<'hello'>; // 'HELLO'
3type Lower = Lowercase<'HELLO'>; // 'hello'
4type Cap = Capitalize<'hello'>; // 'Hello'
5type Uncap = Uncapitalize<'Hello'>; // 'hello'
6
7// Combine with template literals
8type HttpMethod = 'get' | 'post' | 'put' | 'delete';
9type MethodLabel = `HTTP_${Uppercase<HttpMethod>}`;
10// 'HTTP_GET' | 'HTTP_POST' | 'HTTP_PUT' | 'HTTP_DELETE'
11
12// CSS property to camelCase type
13type CSSProperty = 'background-color' | 'font-size' | 'border-radius';
14// Would need custom type for full kebab-to-camel conversionGetter and Setter Types#
1interface Person {
2 name: string;
3 age: number;
4 email: string;
5}
6
7// Generate getter names
8type Getters<T> = {
9 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
10};
11
12type PersonGetters = Getters<Person>;
13// {
14// getName: () => string;
15// getAge: () => number;
16// getEmail: () => string;
17// }
18
19// Generate setter names
20type Setters<T> = {
21 [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
22};
23
24type PersonSetters = Setters<Person>;
25// {
26// setName: (value: string) => void;
27// setAge: (value: number) => void;
28// setEmail: (value: string) => void;
29// }
30
31// Combined
32type WithAccessors<T> = T & Getters<T> & Setters<T>;Route Patterns#
1// API route types
2type ApiVersion = 'v1' | 'v2';
3type Resource = 'users' | 'posts' | 'comments';
4
5type ApiRoute = `/api/${ApiVersion}/${Resource}`;
6// '/api/v1/users' | '/api/v1/posts' | '/api/v1/comments' |
7// '/api/v2/users' | '/api/v2/posts' | '/api/v2/comments'
8
9// With parameters
10type ResourceRoute = `/api/${Resource}/${number}`;
11
12// Extract route params
13type ExtractParams<T extends string> =
14 T extends `${infer _Start}:${infer Param}/${infer Rest}`
15 ? Param | ExtractParams<`/${Rest}`>
16 : T extends `${infer _Start}:${infer Param}`
17 ? Param
18 : never;
19
20type Params = ExtractParams<'/users/:id/posts/:postId'>;
21// 'id' | 'postId'CSS Class Utilities#
1// Tailwind-like utility types
2type Spacing = 0 | 1 | 2 | 4 | 8 | 16;
3type Direction = 't' | 'r' | 'b' | 'l' | 'x' | 'y';
4
5type MarginClass = `m${Direction}-${Spacing}`;
6// 'mt-0' | 'mt-1' | ... | 'my-16'
7
8type PaddingClass = `p${Direction}-${Spacing}`;
9
10// Color utilities
11type ColorName = 'red' | 'blue' | 'green' | 'gray';
12type ColorShade = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
13
14type TextColor = `text-${ColorName}-${ColorShade}`;
15type BgColor = `bg-${ColorName}-${ColorShade}`;
16
17// Responsive prefixes
18type Breakpoint = 'sm' | 'md' | 'lg' | 'xl';
19type ResponsiveClass<T extends string> = T | `${Breakpoint}:${T}`;
20
21type ResponsiveMargin = ResponsiveClass<MarginClass>;
22// 'mt-0' | 'sm:mt-0' | 'md:mt-0' | ...Database Column Types#
1// Table column naming
2type TableName = 'users' | 'posts' | 'comments';
3type ColumnPrefix<T extends string> = `${T}_`;
4
5type UserColumn = `${ColumnPrefix<'user'>}${string}`;
6const col: UserColumn = 'user_id'; // OK
7
8// Foreign key convention
9type ForeignKey<T extends string> = `${T}_id`;
10
11type UserFK = ForeignKey<'user'>; // 'user_id'
12type PostFK = ForeignKey<'post'>; // 'post_id'
13
14// Timestamp columns
15type TimestampColumn = `${string}_at`;
16const created: TimestampColumn = 'created_at'; // OK
17const updated: TimestampColumn = 'updated_at'; // OKPattern Matching#
1// Extract parts of string types
2type ExtractDomain<T extends string> =
3 T extends `${infer _Protocol}://${infer Domain}/${infer _Path}`
4 ? Domain
5 : T extends `${infer _Protocol}://${infer Domain}`
6 ? Domain
7 : never;
8
9type Domain = ExtractDomain<'https://example.com/path'>;
10// 'example.com'
11
12// Parse email
13type ExtractEmailParts<T extends string> =
14 T extends `${infer User}@${infer Domain}`
15 ? { user: User; domain: Domain }
16 : never;
17
18type EmailParts = ExtractEmailParts<'alice@example.com'>;
19// { user: 'alice'; domain: 'example.com' }
20
21// Split string type
22type Split<S extends string, D extends string> =
23 S extends `${infer T}${D}${infer U}`
24 ? [T, ...Split<U, D>]
25 : [S];
26
27type Parts = Split<'a-b-c', '-'>;
28// ['a', 'b', 'c']Action Types (Redux-style)#
1// Action type naming
2type Feature = 'user' | 'post' | 'comment';
3type ActionVerb = 'fetch' | 'create' | 'update' | 'delete';
4type ActionStatus = 'request' | 'success' | 'failure';
5
6type ActionType = `${Uppercase<Feature>}_${Uppercase<ActionVerb>}_${Uppercase<ActionStatus>}`;
7// 'USER_FETCH_REQUEST' | 'USER_FETCH_SUCCESS' | ...
8
9// Generate action creators
10type ActionCreator<T extends string> = {
11 type: T;
12 payload?: unknown;
13};
14
15// Namespace actions
16type NamespacedAction<
17 NS extends string,
18 Action extends string
19> = `[${NS}] ${Action}`;
20
21type UserAction = NamespacedAction<'User', 'Load'>;
22// '[User] Load'Environment Variables#
1// Env var naming convention
2type EnvPrefix = 'VITE' | 'NEXT' | 'REACT_APP';
3type EnvVar<P extends EnvPrefix> = `${P}_${Uppercase<string>}`;
4
5type ViteEnv = EnvVar<'VITE'>;
6
7// Type-safe env access
8type EnvConfig = {
9 VITE_API_URL: string;
10 VITE_APP_TITLE: string;
11 VITE_DEBUG: 'true' | 'false';
12};
13
14function getEnv<K extends keyof EnvConfig>(key: K): EnvConfig[K] {
15 return process.env[key] as EnvConfig[K];
16}
17
18const apiUrl = getEnv('VITE_API_URL'); // stringBest Practices#
Patterns:
✓ Use for event handler names
✓ Generate accessor methods
✓ Type API routes
✓ CSS utility types
String Manipulation:
✓ Uppercase/Lowercase for conventions
✓ Capitalize for camelCase
✓ Combine with key remapping
✓ Pattern matching with infer
Performance:
✓ Limit union combinations
✓ Avoid deep recursion
✓ Cache complex types
✓ Use type aliases
Avoid:
✗ Extremely large unions
✗ Complex nested templates
✗ Runtime string building
✗ Over-engineering simple cases
Conclusion#
Template literal types enable powerful string manipulation at the type level. Use them for event naming conventions, accessor generation, route typing, and CSS utilities. Combine with Uppercase, Lowercase, and Capitalize for consistent naming. Be mindful of union explosion when combining multiple union types in templates.