Template Literal Types allow you to create new string literal types by embedding expressions into string templates.
🧱 Syntax:
type WelcomeMessage = `Hello, ${string}`;This allows you to enforce or construct types that follow specific patterns.
✅ Basic Example
type Color = "red" | "green" | "blue";
type BrightColor = `bright-${Color}`;
// BrightColor = "bright-red" | "bright-green" | "bright-blue"🔠 Capitalize, Lowercase, Uppercase, Uncapitalize
TypeScript offers built-in string manipulation types to use within templates.
type Fruit = "apple" | "banana";
type CapitalFruits = Capitalize<Fruit>; // "Apple" | "Banana"
type Msg = `You chose ${Capitalize<Fruit>}`;
// "You chose Apple" | "You chose Banana"🧠 Combine with Unions
type Sizes = "S" | "M" | "L";
type Variants = "primary" | "secondary";
type ButtonClass = `${Variants}-${Lowercase<Sizes>}`;
// "primary-s" | "primary-m" | "primary-l" | "secondary-s" | ...🔁 Dynamic Key Creation in Mapped Types
Template literal types are especially useful with mapped types:
type Events = "click" | "focus";
type EventHandlers = {
[K in Events as `on${Capitalize<K>}`]: () => void;
};
// EventHandlers:
// {
// onClick: () => void;
// onFocus: () => void;
// }🧪 Use Case: CSS Classes
type Position = "top" | "bottom" | "left" | "right";
type Offset = "0" | "1" | "2";
type MarginClasses = `m-${Position}-${Offset}`;
// e.g., "m-top-0" | "m-bottom-1" | ...<div class="infographic-ts" id="infographic-ts">
<style>
.infographic-ts {
--body-font: system-ui, -apple-system, sans-serif;
--mono-font: ui-monospace, SFMono-Regular, Consolas, "Courier New", monospace;
--ed-bg: #1e1e1e;
--ed-bar: #2a2a2a;
--ed-out: #d4d4d4;
--ed-muted: #8a8a8a;
--ed-ok: #4ec9b0;
--run-bg: #2e7d46;
--run-bg-hover: #35924f;
--tok-kw: #c586c0;
--tok-type: #4ec9b0;
--tok-str: #ce9178;
--tok-punc: #d4d4d4;
--tok-cmt: #808080;
--tok-var: #ffffff;
max-width: 680px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
font-family: var(--body-font);
}
.infographic-ts * { box-sizing: border-box; }
.ts-header { font-family: 'Georgia', 'Fraunces', serif; font-weight: 700;
font-size: 1.9rem; letter-spacing: 0.01em; color: inherit; text-align: center;
margin: 0 0 1.4rem; }
.tabs { display: flex; justify-content: center; gap: 4px; margin-bottom: 1.1rem; flex-wrap: wrap; }
.tab-btn { font-family: var(--body-font); font-size: 0.82rem; font-weight: 500;
background: #ffffff; color: #2563eb; border: 1px solid #2563eb; padding: 0.65rem 1rem;
cursor: pointer; border-radius: 8px; }
.tab-btn.active { background: #2563eb; color: #ffffff; }
.tab-btn:hover:not(.active) { background: #eef3ff; }
.tab-panel { display: none; background: var(--ed-bg); border-radius: 8px;
padding: 1.1rem; }
.tab-panel.active { display: block; }
.code-window { background: var(--ed-bg); border: 1px solid #2f2f2f; border-radius: 8px;
overflow: hidden; margin-bottom: 1rem; }
.code-window:last-child { margin-bottom: 0; }
.code-topbar { display: flex; align-items: center; justify-content: space-between;
padding: 0.5rem 0.75rem; background: var(--ed-bar); }
.code-dots { display: flex; gap: 6px; }
.code-dot { width: 9px; height: 9px; border-radius: 50%; }
.code-dot.r { background: #ff5f57; }
.code-dot.y { background: #febc2e; }
.code-dot.g { background: #28c840; }
.code-filename { font-family: var(--mono-font); font-size: 0.74rem; color: var(--ed-muted); }
.run-btn { display: inline-flex; align-items: center; gap: 0.35rem; background: var(--run-bg);
color: #fff; border: none; border-radius: 4px; padding: 0.32rem 0.7rem; font-size: 0.74rem;
font-family: var(--body-font); font-weight: 500; cursor: pointer; }
.run-btn:hover { background: var(--run-bg-hover); }
.run-btn svg { width: 10px; height: 10px; }
.code-body { padding: 0.85rem 1.1rem; font-family: var(--mono-font); font-size: 0.8rem;
line-height: 1.65; color: var(--ed-out); white-space: pre; overflow-x: auto; }
.tok-kw { color: var(--tok-kw); }
.tok-type { color: var(--tok-type); }
.tok-str { color: var(--tok-str); }
.tok-punc { color: var(--tok-var); }
.tok-cmt { color: var(--tok-cmt); }
.tok-var { color: var(--tok-var); }
.console { padding: 0 1.1rem; max-height: 0; overflow: hidden; font-family: var(--mono-font);
font-size: 0.76rem; line-height: 1.7; transition: max-height 0.35s ease, padding 0.35s ease; }
.console.show { max-height: 260px; padding: 0 1.1rem 0.85rem; }
.console-line { color: var(--ed-ok); word-break: break-word; white-space: pre-wrap; }
.console-lbl { color: var(--ed-muted); }
.note-line { font-family: var(--body-font); font-size: 0.8rem; color: var(--ed-muted);
padding: 0 0.2rem; margin: 0; }
</style>
<h2 class="ts-header">Template literal types</h2>
<div class="tabs">
<button class="tab-btn active" data-tab="t1" type="button">Basic</button>
<button class="tab-btn" data-tab="t2" type="button">Capitalize etc.</button>
<button class="tab-btn" data-tab="t3" type="button">Combine unions</button>
<button class="tab-btn" data-tab="t4" type="button">Mapped keys</button>
<button class="tab-btn" data-tab="t5" type="button">CSS classes</button>
</div>
<div class="tab-panel active" id="t1">
<div class="code-window">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">basic.ts</span>
<button class="run-btn" id="basicBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span class="tok-kw">type</span> <span class="tok-type">Color</span> <span class="tok-punc">=</span> <span class="tok-str">"red"</span> <span class="tok-punc">|</span> <span class="tok-str">"green"</span> <span class="tok-punc">|</span> <span class="tok-str">"blue"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">BrightColor</span> <span class="tok-punc">=</span> <span class="tok-str">`bright-${</span><span class="tok-type">Color</span><span class="tok-str">}`</span><span class="tok-punc">;</span></div>
<div class="console" id="basicConsole"></div>
</div>
<p class="note-line">This is compile-time only — there's no runtime value to log, just a wider string union.</p>
</div>
<div class="tab-panel" id="t2">
<div class="code-window">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">capitalize.ts</span>
<button class="run-btn" id="capBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span class="tok-kw">type</span> <span class="tok-type">Fruit</span> <span class="tok-punc">=</span> <span class="tok-str">"apple"</span> <span class="tok-punc">|</span> <span class="tok-str">"banana"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">CapitalFruits</span> <span class="tok-punc">=</span> <span class="tok-type">Capitalize</span><span class="tok-punc"><</span><span class="tok-type">Fruit</span><span class="tok-punc">>;</span>
<span class="tok-kw">type</span> <span class="tok-type">Msg</span> <span class="tok-punc">=</span> <span class="tok-str">`You chose ${</span><span class="tok-type">Capitalize</span><span class="tok-punc"><</span><span class="tok-type">Fruit</span><span class="tok-punc">></span><span class="tok-str">}`</span><span class="tok-punc">;</span></div>
<div class="console" id="capConsole"></div>
</div>
<p class="note-line">Capitalize, Uncapitalize, Uppercase and Lowercase all operate on the string literal itself.</p>
</div>
<div class="tab-panel" id="t3">
<div class="code-window">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">combine.ts</span>
<button class="run-btn" id="combineBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span class="tok-kw">type</span> <span class="tok-type">Sizes</span> <span class="tok-punc">=</span> <span class="tok-str">"S"</span> <span class="tok-punc">|</span> <span class="tok-str">"M"</span> <span class="tok-punc">|</span> <span class="tok-str">"L"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">Variants</span> <span class="tok-punc">=</span> <span class="tok-str">"primary"</span> <span class="tok-punc">|</span> <span class="tok-str">"secondary"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">ButtonClass</span> <span class="tok-punc">=</span> <span class="tok-str">`${</span><span class="tok-type">Variants</span><span class="tok-str">}-${</span><span class="tok-type">Lowercase</span><span class="tok-punc"><</span><span class="tok-type">Sizes</span><span class="tok-punc">></span><span class="tok-str">}`</span><span class="tok-punc">;</span></div>
<div class="console" id="combineConsole"></div>
</div>
<p class="note-line">Two unions multiplied together — every Variants member paired with every lowercased Sizes member.</p>
</div>
<div class="tab-panel" id="t4">
<div class="code-window">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">event-handlers.ts</span>
<button class="run-btn" id="mapBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span class="tok-kw">type</span> <span class="tok-type">Events</span> <span class="tok-punc">=</span> <span class="tok-str">"click"</span> <span class="tok-punc">|</span> <span class="tok-str">"focus"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">EventHandlers</span> <span class="tok-punc">=</span> <span class="tok-punc">{</span>
<span class="tok-punc">[</span><span class="tok-type">K</span> <span class="tok-kw">in</span> <span class="tok-type">Events</span> <span class="tok-kw">as</span> <span class="tok-str">`on${</span><span class="tok-type">Capitalize</span><span class="tok-punc"><</span><span class="tok-type">K</span><span class="tok-punc">></span><span class="tok-str">}`</span><span class="tok-punc">]:</span> <span class="tok-punc">()</span> <span class="tok-punc">=></span> <span class="tok-type">void</span><span class="tok-punc">;</span>
<span class="tok-punc">};</span></div>
<div class="console" id="mapConsole"></div>
</div>
<p class="note-line">as `on${`Capitalize<K>`}` renames each key while mapping over Events — the values stay () => void.</p>
</div>
<div class="tab-panel" id="t5">
<div class="code-window">
<div class="code-topbar">
<div class="code-dots"><span class="code-dot r"></span><span class="code-dot y"></span><span class="code-dot g"></span></div>
<span class="code-filename">css-classes.ts</span>
<button class="run-btn" id="cssBtn" type="button">
<svg viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
run
</button>
</div>
<div class="code-body"><span class="tok-kw">type</span> <span class="tok-type">Position</span> <span class="tok-punc">=</span> <span class="tok-str">"top"</span> <span class="tok-punc">|</span> <span class="tok-str">"bottom"</span> <span class="tok-punc">|</span> <span class="tok-str">"left"</span> <span class="tok-punc">|</span> <span class="tok-str">"right"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">Offset</span> <span class="tok-punc">=</span> <span class="tok-str">"0"</span> <span class="tok-punc">|</span> <span class="tok-str">"1"</span> <span class="tok-punc">|</span> <span class="tok-str">"2"</span><span class="tok-punc">;</span>
<span class="tok-kw">type</span> <span class="tok-type">MarginClasses</span> <span class="tok-punc">=</span> <span class="tok-str">`m-${</span><span class="tok-type">Position</span><span class="tok-str">}-${</span><span class="tok-type">Offset</span><span class="tok-str">}`</span><span class="tok-punc">;</span></div>
<div class="console" id="cssConsole"></div>
</div>
<p class="note-line">4 positions × 3 offsets — 12 exact class-name strings, none of them typeable by hand without a typo.</p>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-ts')
|| document.querySelector('.infographic-ts');
var root = wrapper;
var tabBtns = root.querySelectorAll('.tab-btn');
var panels = root.querySelectorAll('.tab-panel');
tabBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
var target = btn.getAttribute('data-tab');
tabBtns.forEach(function (b) { b.classList.toggle('active', b === btn); });
panels.forEach(function (p) { p.classList.toggle('active', p.id === target); });
});
});
function line(consoleEl, label, text) {
var el = document.createElement('div');
el.className = 'console-line';
el.innerHTML = '<span class="console-lbl">' + label + ' \u2192</span> ' + text;
consoleEl.appendChild(el);
}
function bind(id, consoleId, cb) {
var btn = root.querySelector('#' + id);
var c = root.querySelector('#' + consoleId);
btn.addEventListener('click', function () {
c.innerHTML = '';
c.classList.add('show');
cb(c);
setTimeout(function () {
c.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, 50);
});
}
bind('basicBtn', 'basicConsole', function (c) {
line(c, 'BrightColor', '"bright-red" | "bright-green" | "bright-blue"');
});
bind('capBtn', 'capConsole', function (c) {
line(c, 'CapitalFruits', '"Apple" | "Banana"');
line(c, 'Msg', '"You chose Apple" | "You chose Banana"');
});
bind('combineBtn', 'combineConsole', function (c) {
line(c, 'ButtonClass', '"primary-s" | "primary-m" | "primary-l" | "secondary-s" | "secondary-m" | "secondary-l"');
});
bind('mapBtn', 'mapConsole', function (c) {
line(c, 'EventHandlers', '{ onClick: () => void; onFocus: () => void }');
});
bind('cssBtn', 'cssConsole', function (c) {
line(c, 'MarginClasses', '"m-top-0" | "m-top-1" | "m-top-2" | "m-bottom-0" | "m-bottom-1" | "m-bottom-2" | "m-left-0" | "m-left-1" | "m-left-2" | "m-right-0" | "m-right-1" | "m-right-2"');
});
})();
</script>
</div>📌 Summary
Feature | Example |
|---|---|
Template literal type |
|
Combine with union types | `error-${"404" |
Capitalize/Uncapitalize |
|
Use with mapped types |
|
Template literal types give you more control over string structures at the type level. They’re heavily used in libraries like Tailwind, GraphQL, tRPC, and design systems for enforcing patterns.