cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Types in JavaScript files

Check plain .js files with TypeScript: turn checking on with // @ts-check or checkJs, write types in JSDoc comments, and hide an error the safe way.

By the end, you can
  • 01
    Turn checking onCheck one JavaScript file with // @ts-check, or every one with checkJs, and say what allowJs adds.
  • 02
    Write types in JSDocGive parameters, return values, variables, and object shapes their types with @param, @returns, @type, and @typedef.
  • 03
    Hide an error safelyChoose between @ts-expect-error, @ts-ignore, and @ts-nocheck, and know what each one hides.

A JavaScript file that checks itself

A checkout page adds 5 to a price for shipping. The price comes from a form, and a form gives you text. The project is plain JavaScript: no .ts files and no build step. TypeScript can still check it:

shipping.js: a JavaScript file with checking on (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** @param {number} price */function addShipping(price) {  return price + 5;} const priceFromForm = "20";console.log(addShipping(priceFromForm));

Line 1, // @ts-check, is a comment that asks TypeScript to check this file. Line 3 is a JSDoc comment: a comment that starts with /**. Its @param {number} price says the parameter price must be a number. Lines 4 to 6 add 5 to it. Line 8 holds the price as text, the way a form gives it. Line 9 passes that text to addShipping, and TypeScript marks it, on purpose here:

  • Argument of type 'string' is not assignable to parameter of type 'number' means "you're passing text to a parameter that has to be a number". An argument is the value you pass in a call.
  • ts(2345) is the error's number, the same as in a .ts file.

The file is still JavaScript. The browser sees two comments, skips them, and runs the file as it is. "20" + 5 joins the two (the Type conversion lesson explains why), so line 9 prints 205. The check happens only in your editor, and in tsc when you run it.

A file shipping.js holds // @ts-check, @param {number} price, and addShipping(priceFromForm). One arrow leads to an editor, where TypeScript reads the JSDoc and marks line 9. Another leads to a browser, which skips the comments and prints 205.shipping.js// @ts-check@param {number} priceaddShipping(priceFromForm)one file, no build stepeditorTypeScript reads the JSDocline 9 is markedbrowserskips the comments205
One file, two readers. TypeScript reads the comments and marks line 9 before the code runs. The browser skips the comments and runs the same shipping.js, which prints 205. Nothing is built in between.
Real-life analogyPencil notes in a recipe book

You write pencil notes in the margin of a family recipe book: "2 eggs, not 3". The cook follows the printed recipe and doesn't need the notes. A friend who checks the recipe reads them and spots where the recipe and the notes disagree. Nobody has to reprint the book.

In real life: Pencil notes in the margin of a recipe
In TypeScript: JSDoc comments above your code
In real life: The cook follows the recipe and skips the notes
In TypeScript: The browser runs the file and skips the comments
In real life: A friend reads the notes and spots "3 eggs, but the note says 2"
In TypeScript: TypeScript reads the JSDoc and marks a value that breaks it
In real life: You never have to reprint the book
In TypeScript: No build step: the .js file stays as it is
The main idea

You can type-check JavaScript without turning it into TypeScript. JSDoc comments, like /** @param {number} price */, hold the types. // @ts-check at the top of one file, or checkJs in tsconfig.json, turns the checking on. The browser skips the comments and runs the file unchanged.

If JSDoc is new to you, the JavaScript lesson Types for JavaScript introduces it. This lesson looks at it from TypeScript's side: what gets checked, which settings turn it on, and how it compares with a .ts file.

Turning on checking for one file

Editors like VS Code run TypeScript on JavaScript files too. They use it for autocomplete and for the types you see when you point at a name. But they don't report type errors in a .js file unless the file asks. The simplest way to ask is one comment on line 1. Try the three choices:

Playground: what line 1 says
// Shipping for the checkout page /** @param {number} price */function addShipping(price) {  return price + 5;} const priceFromForm = "20";console.log(addShipping(priceFromForm));
Line 1 says// Shipping for the checkout page
What happens
TypeScriptNo errors
When it runsprints 205
Try it yourself

Line 1 is an ordinary comment. TypeScript still reads the JSDoc on line 3, but this file never asked to be checked, so nothing is marked. When it runs, it prints 205.

TypeScript checks shipping.js for real each time, as a JavaScript file. Each button changes only line 1. Reset goes back to the plain comment.

With A plain comment, nothing is marked, although the bug is still there. // @ts-check marks line 9. // @ts-nocheck turns reporting off for this file. Each time, the code prints 205: these comments change what TypeScript says, never what JavaScript does.

The comment only counts at the top of the file, before any code. Other comments and blank lines may come before it. Here it comes too late:

A // @ts-check that comes after codePop out in the code editor (opens in a new tab)JavaScript
const shopName = "Tea Corner";// @ts-check /** @type {number} */const itemCount = "3";console.log(shopName, itemCount);

Line 1 is code, so the // @ts-check on line 2 is only a comment. Line 5 breaks its own JSDoc, which says itemCount is a number, and nothing is marked. Line 6 prints Tea Corner 3. Press Edit & run and swap lines 1 and 2: line 5 gets Type 'string' is not assignable to type 'number'.

Once a file is checked, TypeScript works out types from values, the way it does in a .ts file. This is called inference (Annotations & inference covers it):

Checking with no JSDoc at all (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check const cart = { items: 2, total: 7 };cart.items = "3";console.log(cart.totl);

There's no JSDoc here. Line 3 makes a cart, and TypeScript works out that items and total are numbers. Line 4 stores text in items: Type 'string' is not assignable to type 'number'. Line 5 misspells total, and the message ends with the fix: Did you mean 'total'? Both errors are on purpose. When it runs, line 5 prints undefined.

Function parameters are different. A value tells TypeScript its type, but a parameter has no value until someone calls the function:

A parameter with no JSDoc (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check function addShipping(price) {  return price + 5;} console.log(addShipping("20"));console.log(addShipping());

Line 3's price has no type, so TypeScript gives it the type any, which means "anything goes, check nothing" (any, unknown & never covers it). This page checks with strict on, so TypeScript reports it: Parameter 'price' implicitly has an 'any' type. Without strict, you wouldn't even get that.

Line 7 passes text and line 8 passes nothing, and neither is marked. In a JavaScript file, a parameter with no JSDoc is also optional, because plain JavaScript lets you leave arguments out. They print 205 and NaN. A JSDoc type fixes all three problems, and that's the next section.

Writing types with @type and @param

A JSDoc comment starts with /** and sits right above what it describes. It has these parts:

  • Tags that start with @, like @param, which name what is being described.
  • A type in curly braces, like {number}. Inside the braces you write a TypeScript type: number, string[], or "light" | "dark".

Here is a function with three tags:

price.js: a function typed with JSDocPop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** * @param {number} price * @param {number} [percent=10] * @returns {string} */function salePrice(price, percent = 10) {  return (price - (price * percent) / 100).toFixed(2);} const label = salePrice(20);//    ^?console.log(label);

Line 4 says price is a number. Line 5 says percent is a number too: the square brackets make it optional, and =10 notes its default. Line 6, @returns {string}, says what the function gives back; toFixed(2) on line 9 turns a number into text with two decimals. Line 12 calls salePrice(20), so percent is 10, and the query on line 13 shows const label: string. Line 14 prints 18.00.

Point at salePrice on line 8 to see the type TypeScript built from the comment: function salePrice(price: number, percent?: number): string. That's exactly what TypeScript syntax gives:

The same function in price.tsPop out in the code editor (opens in a new tab)TypeScript
function salePrice(price: number, percent: number = 10): string {  return (price - (price * percent) / 100).toFixed(2);} const label = salePrice(20);//    ^?console.log(label);

Line 1 holds the same three facts as the comment: a number price, a number percent that is 10 when left out, and a string result. The query on line 6 gives the same answer, const label: string, and line 7 prints 18.00.

Left, a window price.js holds @param {number} price, @returns {string}, and salePrice(price, percent). Right, a window price.ts holds salePrice(price: number, percent: number = 10): string. A label says TypeScript sees the same types.price.js@param {number} price@returns {string}salePrice(price, percent)types in a commentprice.tssalePrice(price: number,percent: number = 10): stringtypes in the code
The same salePrice, twice. In price.js the types sit in a JSDoc comment; in price.ts they sit in the code. Either way, TypeScript sees a price that is a number and a result that is a string.

@type gives a variable its type. To give a name to an object shape, write @typedef with one @property per property. It does the job of a type alias (Type aliases & interfaces):

cart.js: a named shape with @typedef (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** * @typedef {object} CartItem * @property {string} name * @property {number} price */ /** @type {CartItem[]} */const cart = [  { name: "Tea", price: 3 },  { name: "Cake", price: "4" },];console.log(cart.length);

Lines 3 to 7 name the shape CartItem: a string name and a number price. Line 9 says cart is a list of them. Line 12 gives the cake the text "4", so TypeScript marks it, on purpose: Type 'string' is not assignable to type 'number'. Line 14 prints 2.

Last, the JSDoc way to say "trust me" about a value, the job as does in TypeScript (Type assertions). Write the @type comment right before the value, and put the value in brackets:

coupon.js: telling TypeScript it's an input boxPop out in the code editor (opens in a new tab)JavaScript
// @ts-check const couponBox = /** @type {HTMLInputElement} */ (document.querySelector("#coupon"));//    ^?console.log(couponBox.value);

Line 3 looks up the coupon box. On its own, document.querySelector("#coupon") gives Element | null, because TypeScript can't tell from an id what kind of element it will find. The comment and the brackets around the call say it's an HTMLInputElement, the browser's type for an <input>, and the query on line 4 shows TypeScript believes it. Line 5 prints tea10, the value in this lesson's coupon box. Leave out the brackets and the comment is ignored. Like as, nothing checks this while the page runs.

The table below puts each JSDoc form beside its TypeScript form. Read each row as: what you want to say, how a .js file says it, and how a .ts file says it.

JSDoc and TypeScript, side by side
What you want to sayIn a .js file, with JSDocIn a .ts file
A parameter's type@param {number} priceprice: number
An optional parameter with a default@param {number} [percent=10]percent: number = 10
What a function gives back@returns {string}): string
A variable's type/** @type {string[]} */ above the variableconst tags: string[]
A named object shape@typedef {object} CartItem with one @property per propertytype CartItem = { name: string; price: number }
"Trust me, this is an input box"/** @type {HTMLInputElement} */ (value), with the bracketsvalue as HTMLInputElement

checkJs and allowJs for a whole project

A // @ts-check at the top of every file gets tiring. A project can turn checking on in its tsconfig.json instead (Setting up a project introduced that file):

tsconfig.json for a project with JavaScript filesJSON
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "strict": true,
    "noEmit": true
  },
  "include": ["src"]
}

Line 3, allowJs, lets .js files be part of the project. Line 4, checkJs, reports type errors in all of them, as if each began with // @ts-check. Line 5 turns on the strict checks, and line 6 says "only check, don't write any files". Line 8 includes everything in the src folder.

The two options do different jobs. Many projects have both kinds of file. Here a TypeScript file imports a JavaScript one, which has no // @ts-check. Pick a setting:

Playground: what tsconfig.json turns on
import { addShipping } from "./shipping.js"; const total = addShipping(20);//    ^?console.log(total);console.log(addShipping("20"));
shipping.jsJavaScript
/** @param {number} price */export function addShipping(price) {  return price + 5;} /** @param {number} price */export function shippingLabel(price) {  return "$" + price.toFixd(2);}
SettingsNeither
What TypeScript says
checkout.ts1 error: line 1
shipping.jsNo errors
Line 4 showsconst total: any
Try it yourself

Without allowJs, shipping.js isn't part of the project. Line 1 of checkout.ts gets Could not find a declaration file for module './shipping.js', and the query on line 4 shows total is any, so line 6's text goes through unchecked.

Both files are checked for real with the settings you pick, which the list under each block shows. The code never changes. Reset goes back to Neither.

With Neither, checkout.ts can't see inside shipping.js. TypeScript reports Could not find a declaration file for module './shipping.js' on line 1, and total is any, so the wrong call on line 6 goes through. allowJs brings shipping.js into the project: its JSDoc types reach checkout.ts, total is a number, and line 6 is marked. allowJs + checkJs also reports what's wrong inside shipping.js: line 8 misspells toFixed. Setting only checkJs gives the same result as setting both, because allowJs turns on by itself when checkJs is set.

A tsconfig window holds "allowJs": true and "checkJs": true. An arrow from allowJs leads to checkout.ts, where total is a number. An arrow from checkJs leads to shipping.js, where line 8 is marked.tsconfig"allowJs": true"checkJs": trueone switch per jobcheckout.tsuses the JSDoc typestotal: numbershipping.jsits own errors shownline 8 is marked
allowJs lets checkout.ts read shipping.js, so total is a number. checkJs also reports the errors inside shipping.js, like the misspelled toFixd on line 8.

A single file can still opt out. // @ts-nocheck at its top wins over checkJs:

legacy.js: one file opted outPop out in the code editor (opens in a new tab)JavaScript
// @ts-nocheck /** @type {number} */const itemCount = "3";console.log(itemCount);

The list under the code shows this example sets checkJs: true. Line 4 breaks its @type, but line 1 turns reporting off for this file, so nothing is marked, and line 5 prints 3. That's handy for an old file you plan to fix later. Until you do, it isn't checked at all.

The table below lists every switch in this lesson. Read each row as: what you write, where it goes, and what it does.

Every way to turn checking on or off
What you writeWhere it goesWhat it does
// @ts-checkAt the top of one .js fileReports type errors in that file.
// @ts-nocheckAt the top of one fileReports nothing in that file, even when checkJs is on.
"allowJs": trueIn tsconfig.jsonLets .js files be part of the project, so TypeScript files can import them and use their JSDoc types.
"checkJs": trueIn tsconfig.jsonReports type errors in every .js file in the project, as if each started with // @ts-check. It turns allowJs on too.
// @ts-expect-errorOn the line above an errorHides the next line's error, and is itself an error when that line has none.
// @ts-ignoreOn the line above an errorHides the next line's errors, and stays quiet forever, even when there are none.

@ts-expect-error and @ts-ignore in JavaScript

In Your first typed program you met two comments that hide one line's error. They work the same way in a checked JavaScript file. Say the old coupon form still sends prices as text, and you can't change it yet:

Hiding one known error with @ts-expect-errorPop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** @param {number} price */function addShipping(price) {  return price + 5;} // @ts-expect-error: the old coupon form still sends textconsole.log(addShipping("20"));

Line 8 says "the next line has an error, and I know", with the reason after the colon. Line 9 still passes text, but its error is hidden, so the list is empty. It still prints 205: hiding an error never fixes it. Now someone fixes line 9:

The same comment after line 9 is fixed (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** @param {number} price */function addShipping(price) {  return price + 5;} // @ts-expect-error: the old coupon form still sends textconsole.log(addShipping(20));

Line 9 now passes the number 20 and prints 25. There's nothing left to hide, so TypeScript marks line 8 itself: Unused '@ts-expect-error' directive. A directive is a comment that gives TypeScript an instruction. The error reminds you to delete the comment.

// @ts-ignore in its place would stay quiet, and would also hide any new mistake that ever lands on line 9. That's why @ts-expect-error with a reason is the better choice, in JavaScript files as in TypeScript files. Both hide one line; // @ts-nocheck hides a whole file. Reach for them in this order: fix the code first, then @ts-expect-error with a reason, and the other two only for a short time.

A settings panel with no build step

Small sites, browser extensions, and scripts often ship their .js files exactly as written. JSDoc lets them keep that and still be checked. Here is a settings panel. The reader types a font size in a box, which holds 18 on this lesson's page:

settings.js: two bugs TypeScript finds (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** * @typedef {object} Settings * @property {"light" | "dark"} theme * @property {number} fontSize */ /** @type {Settings} */const settings = { theme: "dark", fontSize: 16 }; const sizeBox = /** @type {HTMLInputElement} */ (document.querySelector("#font-size"));settings.fontSize = sizeBox.value;settings.theme = "blue";console.log(settings.fontSize + 2);

Lines 3 to 7 name the Settings shape: a theme that is "light" or "dark" (Literal types covers types like these), and a number fontSize. Line 10 makes the settings. Line 12 reads the box with the JSDoc cast from earlier. Line 13 stores the box's value, which is always text, where a number goes. Line 14 picks a theme that doesn't exist. Both are marked on purpose. Run it, and line 15 prints 182, because "18" + 2 joins text.

settings.js, fixedPop out in the code editor (opens in a new tab)JavaScript
// @ts-check /** * @typedef {object} Settings * @property {"light" | "dark"} theme * @property {number} fontSize */ /** @type {Settings} */const settings = { theme: "dark", fontSize: 16 }; const sizeBox = /** @type {HTMLInputElement} */ (document.querySelector("#font-size"));settings.fontSize = Number(sizeBox.value);settings.theme = "light";console.log(settings.fontSize + 2);

Line 13 turns the text into a number with Number(…), and line 14 picks "light". No errors are left, and line 15 prints 20. The browser still runs this exact file, comments and all.

Error or fine?
  • /** @type {number} */ let count = "3";
  • /** @type {number} */ let count = Number("3");
  • /** @type {"light" | "dark"} */ let theme = "blue";
  • /** @param {string} name */ function greet(name) {} greet();
  • function greet(name = "Asha") {} greet();
  • let total: number = 7;
  • const box = /** @type {HTMLInputElement} */ (document.querySelector("#coupon"));
  • /** @type {string[]} */ const tags = [];
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it in a checked .js file. 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

One mix-up is worth seeing in code. TypeScript's own syntax doesn't belong in a .js file:

TypeScript syntax in a .js file (an error on purpose)Pop out in the code editor (opens in a new tab)JavaScript
// @ts-check let total: number = 7;console.log(total);

Line 3 writes : number, which is TypeScript syntax. TypeScript says Type annotations can only be used in TypeScript files. The browser can't run the file at all: it stops with a SyntaxError before line 4. In a .js file, types go in JSDoc comments.

  • "JSDoc comments change what my code does." No. They're comments, and the browser skips them. addShipping("20") prints 205 with or without them.
  • "// @ts-check works anywhere in the file." No. It has to come before any code. After a line of code, it's only a comment.
  • "allowJs checks my JavaScript files." No. It lets them into the project, so TypeScript files can use their types. checkJs or // @ts-check reports their errors.
  • "To get type checking, I have to rename my files to .ts." No. A .js file with JSDoc and // @ts-check is checked too, with no build step.
  • "A JSDoc cast checks the element." No. /** @type {HTMLInputElement} */ (…) is trusted, like as. If the element isn't an input box, nothing warns you.
  • "@ts-ignore and @ts-expect-error are the same." No. Both hide the next line's error, but only @ts-expect-error tells you when there's nothing left to hide.

Practice exercises

Every exercise is a JavaScript file. When you fix code, press Edit & run on the starter code: TypeScript checks it as a .js file while you type, and the console shows what it prints. Try a guess first, then use the hints.

Exercise 1 · Warm-upFind the line TypeScript marks

This file is checked. Which line will TypeScript mark?

Starter codePop out in the code editor (opens in a new tab)JavaScript
// @ts-check

/** @type {{ name: string, price: number }[]} */
const cart = [
  { name: "Tea", price: 3 },
  { name: "Cake", price: "4" },
];

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

    Exercise 2 · Warm-upRead a JSDoc return type

    What type does TypeScript give label? Guess first. Then point at label to check, or press Edit & run and add a // ^? line under it.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    // @ts-check /** * @param {number} count * @returns {string} */function itemLabel(count) {  return count === 1 ? "1 item" : `${count} items`;} const label = itemLabel(3);console.log(label);

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

      Exercise 3 · PracticeTurn checking on, then fix the bug

      This file has a bug and a JSDoc type that would catch it, but TypeScript reports nothing. Make TypeScript report the bug, then fix the call. What does your fixed code print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      /** @param {number} quantity */function addToCart(quantity) {  return quantity + 1;} console.log(addToCart("2"));

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

        Exercise 4 · PracticeWrite the missing @param

        Add a JSDoc comment so price is a number and the error goes away. What does the code print once the error is gone?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        // @ts-check function formatPrice(price) {  return "$" + price.toFixed(2);} console.log(formatPrice(4.5));

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

          Exercise 5 · ChallengeA comment that outlived its reason

          Line 9 was fixed last week, but the comment above it stayed. Predict what TypeScript says about line 8, then make the file clean. Which error number does TypeScript report on line 8?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          // @ts-check
          
          /** @param {number} price */
          function addShipping(price) {
            return price + 5;
          }
          
          // @ts-expect-error: the old coupon form still sends text
          console.log(addShipping(20));

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

            Exercise 6 · ChallengeRead the coupon box in a JavaScript file

            TypeScript marks line 4 twice. Fix both errors by changing only line 3, with a JSDoc comment. The page has a coupon box holding tea10. What does your fixed code print?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            // @ts-check const couponBox = document.querySelector("#coupon");console.log(couponBox.value.toUpperCase());

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

              Check your understanding

              For each question, keep two moments apart: what TypeScript says before the code runs, and what the browser does when it runs the file. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The Types in JavaScript files quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does // @ts-check at the top of a .js file do?

                Choose an answer to see the explanation.

              2. Question 2 of 7Which line does TypeScript mark in theme.js?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // @ts-check
                
                /** @type {"light" | "dark"} */
                let theme = "dark";
                theme = "light";
                theme = "blue";

                Choose an answer to see the explanation.

              3. Question 3 of 7This file has no // @ts-check, and the project doesn't set checkJs. What does TypeScript report?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                /** @param {number} price */
                function addShipping(price) {
                  return price + 5;
                }
                
                console.log(addShipping("20"));

                Choose an answer to see the explanation.

              4. Question 4 of 7What type does TypeScript give box on line 3?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // @ts-check
                
                const box = /** @type {HTMLInputElement} */ document.querySelector("#coupon");

                Choose an answer to see the explanation.

              5. Question 5 of 7Which tsconfig.json setting reports errors in every JavaScript file of a project?

                Choose an answer to see the explanation.

              6. Question 6 of 7You fixed the line under a // @ts-expect-error comment. What does TypeScript say now?

                Choose an answer to see the explanation.

              7. Question 7 of 7TypeScript marks line 8 of this checked file. What does it print when it runs anyway?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // @ts-check
                
                /** @param {number} quantity */
                function addOne(quantity) {
                  return quantity + 1;
                }
                
                console.log(addOne("2"));

                Choose an answer to see the explanation.

              Key takeaways

              • TypeScript can check plain .js files. The types go in JSDoc comments, so the browser runs the file unchanged.
              • // @ts-check before any code checks one file; checkJs in tsconfig.json checks them all; // @ts-nocheck opts one file out.
              • allowJs lets .js files into a project, so TypeScript files can import them and use their JSDoc types.
              • @param, @returns, @type, and @typedef hold TypeScript types in curly braces.
              • /** @type {T} */ (value), with the brackets, is the JSDoc form of as, and it's just as unchecked.
              • Prefer @ts-expect-error with a reason over @ts-ignore: it tells you when the error it hides is gone.

              Remember the one-liner.
              JSDoc holds the types, // @ts-check or checkJs turns on the errors, and the browser runs the same .js file.

              Coming next: Typing the DOM. It covers the types of the page itself: elements, what querySelector gives back, and the null a lookup can return.

              CompleteFrontend Clear concepts. Working examples.