cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Template literal types

Build string types from patterns: class names like btn-small, sizes like 120px, handler names like onClick, and event names that TypeScript checks for you.

By the end, you can
  • 01
    Write a string pattern as a typeBuild string types like btn-${Size} and ${number}px, and read the error when a string doesn't fit.
  • 02
    Predict the names a union makesWork out every string a template with unions makes, and change case with Uppercase, Lowercase, and Capitalize.
  • 03
    Type event namesBuild event and handler names from an object's keys, and let TypeScript find the handler's value type from the name.

A string type with a pattern

A shop's buttons get their look from CSS classes. There are two sizes, so there are two classes: btn-small and btn-large. Any other class name, like a typo, gives a button with no style at all. You already know how to list the sizes as a union of literal types. Now you can build the class names from them:

Class names built from a pattern (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Size = "small" | "large";type ButtonClass = `btn-${Size}`;//   ^?const saveButton: ButtonClass = "btn-smal";console.log(saveButton);

Line 1 says a Size is the text "small" or the text "large". Line 2 is the new part. It looks like a JavaScript template literal, with backticks and a ${ } placeholder, but it's a type. It means "the text btn-, followed by a Size". This is called a template literal type. The Mapped types lesson used one to rename keys, `${Key}Label`; this lesson shows what they can do.

The query on line 3 shows what TypeScript made of it: "btn-small" | "btn-large", exactly two strings. Line 4 stores "btn-smal", with an l missing. TypeScript marks it, on purpose here, because it's the bug:

  • Type '"btn-smal"' is not assignable to type '"btn-small" | "btn-large"' means "this text isn't one of the two class names". "Assignable" means "allowed to be stored there".
  • Did you mean '"btn-small"'? is TypeScript's guess at the fix, because the text is very close to one of the names. This version of the error has the number ts(2820).

Line 5 still prints btn-smal when it runs: the type is a check before the code runs, not a change to it. Now type your own class name for line 4 and watch the list under the code:

Playground: type the button's class name
A class name for the Save buttonPop out in the code editor (opens in a new tab)TypeScript
type Size = "small" | "large";type ButtonClass = `btn-${Size}`;//   ^?const saveButton: ButtonClass = "btn-smal";console.log(saveButton);
Line 4 holds"btn-smal"
What happens
TypeScript1 error: line 4
When it runsprints btn-smal
Try it yourself

"btn-smal" is missing an l, so it isn't one of the two names in ButtonClass. TypeScript marks line 4 and even suggests the fix: Did you mean '"btn-small"'?

The code is checked by the real TypeScript compiler each time you type. Only line 4 changes. Reset puts "btn-smal" back.

"btn-small" and "btn-large" have no errors. "btn-medium" gets one, because medium isn't a size. "BTN-SMALL" gets one too: capital letters make a different string. And "small" is missing the fixed text btn- that every name starts with.

Two cards on the left, small and large, are the members of Size. Arrows lead from both into a card in the middle holding the pattern btn-${Size}. Two arrows lead out to two cards on the right, btn-small and btn-large, the names in ButtonClass.Size"small""large"`btn-${Size}`the patternButtonClass"btn-small""btn-large"one name for each size
TypeScript puts each member of Size into the placeholder, one at a time. Two sizes make two names, and ButtonClass is exactly those two strings.
Real-life analogyA stamp with a word wheel

An office stamp has some fixed letters and a small wheel you turn to pick a word. The wheel has only a few words carved on it. Whatever you do, every print is the fixed letters followed by one of those words. You could list every possible print before you ever press the stamp on paper.

In real life: The fixed letters on the stamp, "btn-"
In TypeScript: The fixed text btn- in the pattern
In real life: The wheel, which shows only the words carved on it
In TypeScript: The placeholder, which takes only the members of Size
In real life: Every print the stamp can make
In TypeScript: Every string in ButtonClass: "btn-small" and "btn-large"
In real life: A word that isn't on the wheel can't be printed
In TypeScript: "btn-smal" isn't in the type, so it's an error
The main idea

A template literal type is a string type built from a pattern. You write it like a template literal, with types in the placeholders: `btn-${Size}`. TypeScript works out every string the pattern allows and marks any other string before the code runs.

Template types look like template literals

Here is the same idea as plain JavaScript, with a value instead of a type:

A JavaScript template literal makes one stringPop out in the code editor (opens in a new tab)JavaScript
const size = "small";
const className = `btn-${size}`;
console.log(className);

Line 1 stores the text "small". Line 2 puts the value of size into the placeholder while the code runs. Line 3 prints btn-small: one string. A template literal type uses the same backticks, but its placeholder holds a type, and the result is a type: every string that fits.

The table below compares the two. Read each row as a question, then the answer for each.

A template literal and a template literal type
QuestionA template literal (a value)A template literal type (a type)
Where do you write it?In code that runs, like const className = ....In a type, like type ButtonClass = ....
What goes inside ${ }?A value, like the variable size.A type, like Size, string, or number.
What do you get?One string, like "btn-small".A string type: every string that fits the pattern.
When does it happen?While the code runs.Before the code runs. The type is removed after the check.

The placeholder doesn't have to hold a union. It can hold number, which makes a pattern for CSS sizes:

Sizes in pixels (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Pixels = `${number}px`; const width: Pixels = "120px";const gap: Pixels = "1.5px";const height: Pixels = "tall";const margin: Pixels = 120;

Line 1 says a Pixels value is text that reads as a number, followed by px. Lines 3 and 4 fit: 120 and 1.5 both read as numbers. TypeScript marks two lines on purpose:

  • Line 5, "tall": Type '"tall"' is not assignable to type '`${number}px`'. The text tall doesn't read as a number.
  • Line 6, 120: Type '120' is not assignable to the same type. A template literal type is always a string type, and 120 without quotes is a number, not text.

A string placeholder takes any text at all. User IDs that must start with user- look like this:

IDs that start with user- (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type UserId = `user-${string}`; const asha: UserId = "user-asha";const sam: UserId = "user-42";const guest: UserId = "guest-7";

Line 1 says a UserId is user- followed by any text. Lines 3 and 4 fit, whatever comes after the dash. Line 5 starts with guest-, so TypeScript marks it on purpose: the fixed text has to be there exactly.

The table below lists what each kind of placeholder lets in. Read each row as: this type in the placeholder, then which strings fit.

What fits each kind of placeholder
In the placeholderWhich strings fitAn example
A union of strings, like SizeOnly the union's members, one at a time."btn-small" fits the pattern btn-${Size}.
stringAny text at all, even no text."user-asha" and even "user-" fit user-${string}.
numberText that reads as a number, like 120 or 1.5."120px" and "1.5px" fit ${number}px.
booleanOnly the text true or the text false."true" fits ${boolean}, but "yes" doesn't.

A union fills in every combination

A pattern can have more than one placeholder. A toast is a small message that pops up in a corner of the page, like "Saved". Its position is a side and an alignment:

Four corners for a toast messagePop out in the code editor (opens in a new tab)TypeScript
type Side = "top" | "bottom";type Align = "left" | "right";type ToastPosition = `${Side}-${Align}`;//   ^?const toast: ToastPosition = "bottom-right";console.log(toast);

Line 1 lists two sides and line 2 two alignments. Line 3 joins a side, a dash, and an alignment. The query on line 4 shows that TypeScript made every combination: "top-left", "top-right", "bottom-left", and "bottom-right". Line 5 picks one of them, and line 6 prints bottom-right.

A grid. Across the top are the alignments left and right. Down the left side are the sides top and bottom. Each cell joins its row and column: top-left, top-right, bottom-left, and bottom-right.Align"left""right"Side"top""bottom""top-left""top-right""bottom-left""bottom-right"2 sides times 2 alignments: 4 names
Each side meets each alignment once. Two sides and two alignments make four names in ToastPosition.

Change the sides on line 1, and watch the answer after the query on line 4:

Playground: how many toast positions?
Where a toast message can appearPop out in the code editor (opens in a new tab)TypeScript
type Side = "top" | "bottom";type Align = "left" | "right";type ToastPosition = `${Side}-${Align}`;//   ^?const toast: ToastPosition = "bottom-right";console.log(toast);
ToastPosition holds4 names
What happens
Sides times alignments2 × 2 = 4
TypeScriptNo errors
Try it yourself

Two sides and two alignments make 4 names. "bottom-right" is one of them, so line 5 has no error.

TypeScript checks the code for real, and the answer after ^? on line 4 lists every name. Each button changes only line 1. Reset goes back to Top and bottom.

With Only top, there are 2 names, and line 5's "bottom-right" isn't one of them. With Add center, there are 6: the count is always the number of sides times the number of alignments. Each union you add multiplies again, so the count grows fast. TypeScript refuses a pattern that would make 100,000 names or more:

A pattern with too many names (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";type PostalCode = `${Digit}${Digit}${Digit}${Digit}${Digit}`;

Line 1 lists the ten digits. Line 2 asks for five of them in a row, like a five-digit postal code. That is 10 × 10 × 10 × 10 × 10, which is 100,000 names, so TypeScript marks line 2 on purpose: Expression produces a union type that is too complex to represent. For a part that can be almost anything, put string or number in the placeholder instead of a long union.

Uppercase, Lowercase, and Capitalize

Names in code often need a capital letter where the data has none. React components, for example, take handler props like onClick for the click event. TypeScript has four built-in helpers that change the case of a string type. You write the helper's name and then a string type in angle brackets, the way you pass a type to a generic:

Four helpers that change casePop out in the code editor (opens in a new tab)TypeScript
type Loud = Uppercase<"save">;//   ^?type Quiet = Lowercase<"SAVE">;//   ^?type Title = Capitalize<"save">;//   ^?type Plain = Uncapitalize<"Save">;//   ^?

Line 1 turns "save" into "SAVE", as the query on line 2 shows. Line 3 does the opposite. Line 5 changes only the first letter, so the query on line 6 shows "Save", and line 7 makes the first letter small again.

The table below sums them up. Read each row as: this helper, what it does, and an example.

The four case helpers
HelperWhat it does to the textAn example
UppercaseMakes every letter a capital.Uppercase<"save"> is "SAVE".
LowercaseMakes every letter small.Lowercase<"SAVE"> is "save".
CapitalizeMakes only the first letter a capital.Capitalize<"save"> is "Save".
UncapitalizeMakes only the first letter small.Uncapitalize<"Save"> is "save".

The helpers work inside a pattern, too. Here line 2 builds handler names from a button's events. Pick a different helper and watch whether line 4's "onClick" still fits:

Playground: pick a helper for the handler names
Handler names for a buttonPop out in the code editor (opens in a new tab)TypeScript
type ButtonEvent = "click" | "focus";type HandlerName = `on${Capitalize<ButtonEvent>}`;//   ^?const handler: HandlerName = "onClick";console.log(handler);
HandlerName is"onClick" | "onFocus"
What happens
Line 4 asks for"onClick"
TypeScriptNo errors
Try it yourself

Capitalize turns the first letter into a capital: "click" becomes "Click". The names are "onClick" and "onFocus", so line 4 is fine.

TypeScript checks the code for real. Each button changes only line 2, and line 4 always asks for onClick. Reset goes back to Capitalize.

Only Capitalize makes "onClick". Uppercase makes "onCLICK", and Lowercase or no helper leaves "onclick". Strings are case-sensitive, so each of those is a different name, and line 4 gets an error.

The helpers change types. They don't know about the string methods you call while the code runs:

A method that changes case gives back a stringPop out in the code editor (opens in a new tab)TypeScript
const loud = "save".toUpperCase();//    ^?console.log(loud);

Line 1 calls .toUpperCase() on "save", and line 3 prints SAVE. But the query on line 2 shows const loud: string, not "SAVE". TypeScript knows the method gives back some string. It doesn't work out which one.

Typed event names

A profile page lets a user change their name, email, and age, and other parts of the page listen for those changes. The events are called nameChanged, emailChanged, and ageChanged. You could list them by hand, but then a new property needs a new event name too. Build them from the object type instead:

An event name for every property (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; email: string; age: number };type ProfileEvent = `${keyof Profile}Changed`;//   ^? function on(event: ProfileEvent, handler: () => void) {  console.log(`Listening for ${event}`);} on("nameChanged", () => {});on("name", () => {});

Line 1 describes a profile. On line 2, keyof Profile means "the names of Profile's properties", the union "name" | "email" | "age". The pattern adds Changed to each, and the query on line 3 shows the three event names. Lines 5 to 7 make an on function that takes one of them. This one only prints what it listens for; a real one would save the handler and call it later.

Line 9 passes "nameChanged", which fits. Line 10 passes the property name "name", a common slip, and TypeScript marks it on purpose: Argument of type '"name"' is not assignable to parameter of type '"nameChanged" | "emailChanged" | "ageChanged"'. An argument is a value you pass to a function, and a parameter is the name that receives it. The code still prints both lines when it runs.

The handler doesn't know what changed yet: a new age is a number, but a new name is text. TypeScript can work that out from the event name. This uses a type parameter K with a keyof constraint, as in the Generic constraints lesson:

The handler's value follows the event namePop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; email: string; age: number }; function on<K extends keyof Profile>(  event: `${K}Changed`,  handler: (value: Profile[K]) => void,) {  console.log(`Listening for ${event}`);} on("ageChanged", (value) => console.log(value));//                ^?

Line 3 says K must be one of Profile's property names. Line 4 says the event is K followed by Changed. Line 5 says the handler gets a value of type Profile[K], the type of the property that K names. Line 10 passes "ageChanged". TypeScript asks which K makes `${K}Changed` equal "ageChanged", and finds "age". So the query on line 11 shows (parameter) value: number. When it runs, line 7 prints Listening for ageChanged.

Three cards in a row joined by arrows. The first holds the event name "ageChanged". The second holds K = "age", found by matching the pattern ${K}Changed. The third holds value: number, found by looking up Profile[K]."ageChanged"the event you passK = "age"1. match the patternvalue: number2. look up Profile[K]
TypeScript reads the name you pass: "ageChanged" fits the pattern when K is "age". Then Profile["age"] gives the type of value.

Pass a different event name on line 10 and watch the type of value:

Playground: pass a different event name
Listen for one profile changePop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; email: string; age: number }; function on<K extends keyof Profile>(  event: `${K}Changed`,  handler: (value: Profile[K]) => void,) {  console.log(`Listening for ${event}`);} on("ageChanged", (value) => console.log(value));//                ^?
value's typenumber
What happens
Event name"ageChanged"
TypeScriptNo errors
Try it yourself

TypeScript matches "ageChanged" against the pattern on line 4, so K is "age". Profile[K] is the type of age, number, and that's the type of value.

TypeScript checks the code for real, and the answer after ^? on line 11 is the type of value. Each button changes only the event name on line 10. Reset goes back to ageChanged.

"nameChanged" and "emailChanged" give a string. "age" and "ageChange" don't fit the pattern, so line 10 gets an error, and TypeScript can only say value is string | number.

Analytics events and handler names

Shops record what shoppers do, like opening the cart, in an analytics tool. The names have to be exactly right, or the reports miss them. A pattern keeps them right:

Analytics event names (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Area = "cart" | "checkout";type Action = "open" | "close";type AnalyticsEvent = `${Area}:${Action}`; function track(event: AnalyticsEvent) {  console.log(`Tracked ${event}`);} track("cart:open");track("checkout:close");track("cart:opne");

Lines 1 and 2 list the areas and the actions. Line 3 joins them with a colon, which makes four names, from "cart:open" to "checkout:close". Lines 5 to 7 make a track function that takes only those names. Lines 9 and 10 pass real names. Line 11 has two letters swapped, "cart:opne", and TypeScript marks it on purpose. Without the type, this typo would print Tracked cart:opne and send a name nobody looks for.

Sometimes your code builds the name while it runs, such as a handler name from an event. As you saw, string methods give back only string. When you're sure what the code makes, you can tell TypeScript with as:

A handler name built while the code runsPop out in the code editor (opens in a new tab)TypeScript
type FormEvent = "submit" | "reset"; function handlerName<E extends FormEvent>(event: E) {  const name = `on${event[0].toUpperCase()}${event.slice(1)}`;  return name as `on${Capitalize<E>}`;} const submitHandler = handlerName("submit");//    ^?console.log(submitHandler);

Line 3 makes a function for one form event, E. Line 4 builds the name: on, the first letter in capitals, then the rest of the event. Line 5 says "this string is `on${Capitalize<E>}`". TypeScript trusts you here, so line 4 must really do what the type says. Line 8 calls it with "submit", the query on line 9 shows the exact type "onSubmit", and line 10 prints onSubmit.

Error or fine?
  • const button: `btn-${"small" | "large"}` = "btn-large";
  • const button: `btn-${"small" | "large"}` = "btn-medium";
  • const width: `${number}px` = "40px";
  • const width: `${number}px` = "40";
  • const handler: `on${Capitalize<"click">}` = "onClick";
  • const handler: `on${Capitalize<"click">}` = "onclick";
  • const id: `user-${string}` = "user-";
  • const flag: `${boolean}` = "yes";
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it. Each line is checked on its own.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

A template literal type checks strings that TypeScript can see in your code. Text that arrives while the page runs is different. Here a shopper types a width into a box:

A width typed into a box (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Pixels = `${number}px`; const widthInput = document.createElement("input");widthInput.value = "120px"; const width: Pixels = widthInput.value;console.log(width);

Line 3 makes an <input> text box, and line 4 puts 120px in it, as if the shopper typed it. Line 6 stores the box's value as Pixels, and TypeScript marks it on purpose: Type 'string' is not assignable to type '`${number}px`'. A text box can hold any text, so TypeScript only knows the value is a string. It can't know the shopper typed something that fits. The fix is to check the text while the code runs:

Check the text, then use itPop out in the code editor (opens in a new tab)TypeScript
type Pixels = `${number}px`; function isPixels(text: string): text is Pixels {  return /^\d+(\.\d+)?px$/.test(text);} const widthInput = document.createElement("input");widthInput.value = "120px"; const text = widthInput.value;if (isPixels(text)) {  const width: Pixels = text;  console.log(width);}

Lines 3 to 5 make isPixels, a type guard. Line 4 tests the text with a regular expression: digits, maybe a decimal part, then px. Its return type, text is Pixels, tells TypeScript that a true answer means the text fits. So inside the if on line 11, line 12 can store it as Pixels, and line 13 prints 120px.

The table below lists where text comes from. Read each row as: text from here, then what TypeScript knows about it.

What TypeScript knows about a string
Where the text comes fromWhat TypeScript knows about it
A string written in your code, like "120px"Its exact text. TypeScript checks it against Pixels before the code runs.
Text from an input, a server, or the address barOnly that it's a string. It can't go where Pixels is needed until your code checks it.
Text your code builds with .toUpperCase() or +Usually only string. Check it, or tell TypeScript with as when you're sure.
  • "A template literal type makes a string." No. It describes which strings are allowed. Your code still writes or builds every string, and the type is gone when the code runs.
  • "TypeScript checks what users type against the pattern." No. Text from an input or a server is only a string to TypeScript. Check it with your own code, like isPixels.
  • "Uppercase changes my values." No. It changes types. To change a value, call .toUpperCase(), whose type is just string.
  • "Two unions in a pattern add up." No. They multiply: 2 sides and 3 alignments make 6 names, not 5.
  • "`${number}px` takes the number 120." No. It takes text, like "120px". Every template literal type is a string type.
  • "Case doesn't matter in a pattern." No. "onclick" and "onClick" are different strings, which is why the case helpers exist.

Practice exercises

Each exercise is TypeScript. To change the starter code, press Edit & run: the errors update as you type, and a // ^? line under a type shows TypeScript's answer. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead the names a pattern makes

What strings does GreetingKey contain? Guess first. Then press Edit & run and add a // ^? line under line 2 to check.

Starter codePop out in the code editor (opens in a new tab)TypeScript
type Lang = "en" | "fr";type GreetingKey = `greeting-${Lang}`;

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the class name that doesn't fit

    Two theme classes, one of them wrong. Which line will TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type Theme = "light" | "dark";
    type ThemeClass = `theme-${Theme}`;
    
    const page: ThemeClass = "theme-dark";
    const card: ThemeClass = "dark-theme";

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWrite a type for CSS sizes

      Size is just string, so the broken margin on line 5 gets no error. Change line 1 so that sizes in px and rem are allowed and anything else is an error. Which line gets the error then?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type Size = string; const gap: Size = "8px";const padding: Size = "1.5rem";const margin: Size = "large";

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeMake the handler names match

        A modal window has open and close events, and its props are named like onOpen. TypeScript marks line 4. Change only line 2 so that "onOpen" fits.

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type ModalEvent = "open" | "close";type ModalHandler = `on${ModalEvent}`; const handler: ModalHandler = "onOpen";console.log(handler);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFix the cart event name

          A cart page listens for an update to its total. TypeScript marks line 8. What event name should it pass?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type Cart = { items: number; total: number };type CartEvent = `${keyof Cart}Updated`; function on(event: CartEvent, handler: () => void) {  console.log(`Listening for ${event}`);} on("totalUpdate", () => {});

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeGive the handler the right value type

            The handler's value is unknown, a type that lets you do nothing with it until you check it, so line 8 gets 'value' is of type 'unknown'. Change on so that value gets the right type from the event name. What type does it get for "couponChanged"?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Cart = { items: number; coupon: string }; function on(event: `${keyof Cart}Changed`, handler: (value: unknown) => void) {  console.log(`Listening for ${event}`);} on("couponChanged", (value) => {  console.log(value.toUpperCase());});

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, write out the strings the pattern makes before you choose. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The template literal types quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does SizeLabel contain?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Size = "S" | "M";
                type SizeLabel = `size-${Size}`;

                Choose an answer to see the explanation.

              2. Question 2 of 7How many names does this ToastPosition hold?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Side = "top" | "bottom";
                type Align = "left" | "center" | "right";
                type ToastPosition = `${Side}-${Align}`;

                Choose an answer to see the explanation.

              3. Question 3 of 7What is the Handler type?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Handler = `on${Capitalize<"click">}`;

                Choose an answer to see the explanation.

              4. Question 4 of 7What type does TypeScript give loud, and what does line 2 print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const loud = "tea".toUpperCase();
                console.log(loud);

                Choose an answer to see the explanation.

              5. Question 5 of 7Which line does TypeScript flag in this profile event code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Profile = { name: string; age: number };
                type ProfileEvent = `${keyof Profile}Changed`;
                
                function on(event: ProfileEvent) {
                  console.log(event);
                }
                
                on("ageChanged");
                on("age");

                Choose an answer to see the explanation.

              6. Question 6 of 7Does TypeScript accept the width on line 2?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Pixels = `${number}px`;
                const width: Pixels = "2.5px";

                Choose an answer to see the explanation.

              7. Question 7 of 7What is left of type ButtonClass when the page runs?

                Choose an answer to see the explanation.

              Key takeaways

              • A template literal type is written like a template literal, with types in the placeholders, like `btn-${Size}`.
              • A union in a placeholder makes one string per member. Two unions make every combination, so the counts multiply.
              • string and number in a placeholder make open patterns, like `${number}px`. boolean allows only the text "true" or "false".
              • Uppercase, Lowercase, Capitalize, and Uncapitalize change the case of string types, not values.
              • `${keyof T}Changed` builds event names from an object's keys, and a generic K lets the handler's value follow the name.
              • Text that arrives while the page runs is only a string. Check it before you use it as a pattern type.

              Remember the one-liner.
              A template literal type is a pattern for strings, and TypeScript checks every string in your code against it.

              Coming next: Partial, Pick, Omit & friends. It covers TypeScript's built-in helpers that make new object types from old ones, like every property optional for a form update.

              CompleteFrontend Clear concepts. Working examples.