๐ง๐๐ฝ๐ฒ๐ฆ๐ฐ๐ฟ๐ถ๐ฝ๐ ๐ง๐ฒ๐บ๐ฝ๐น๐ฎ๐๐ฒ ๐๐ถ๐๐ฒ๐ฟ๐ฎ๐น ๐ง๐๐ฝ๐ฒ๐
Strings cause many bugs in TypeScript.
You might pass "400px" when a function needs "4rem". You might type "onclick" instead of "onClick". You might build a broken URL by forgetting a slash.
Most developers use runtime validation or hope for the best. You can do better. Use template literal types to enforce string patterns at compile time.
This is not just a basic union like "up" | "down". Template literal types let you build and restrict strings dynamically.
Use them for CSS values:
type CSSSize = ${number}px | ${number}rem | ${number}% | "auto";
Now, TypeScript knows the difference: โข setSize("16px") is OK. โข setSize("16") is an error. โข setSize("banana") is an error.
You get safety without runtime overhead.
Use them for event handlers:
type EventName = "click" | "change" | "submit";
type EventHandleron${Capitalize<E>};
This turns "click" into "onClick" automatically. If you add a new event, the handler type updates itself. This creates a single source of truth.
Use them for API routes:
type ApiRoute = /api/${string} | /api/${string}/${string};
You can even use the "infer" keyword to parse strings. This lets you pull parameters out of a URL path at the type level. It is like running a split function, but during compilation.
Follow these rules to avoid trouble:
- Use simple unions for small, fixed lists.
- Avoid massive combinations. A union with billions of members will crash the compiler.
- Do not use these for runtime validation. These types disappear once your code runs. Use Zod or Valibot for user input.
- Do not try to parse complex languages like SQL. You will hit recursion limits.
Template literal types turn strings into contracts. They catch mistakes before they reach your users.
What is your experience with template literal types? Have you found a clever pattern or hit a limit?