Complete HTML Course Module 15: Advanced: Best Practices and Final Project

HTML Character Encoding: UTF-8 and Charsets Explained

  • Beginner
  • Lesson 15.3
  • 6 min read
  • 66 of 69 in the course

In short

HTML character encoding tells the browser how to turn the bytes in your file into letters. Learn what a charset is, why UTF-8 is the right choice, and how to fix broken characters. You met <meta charset="utf-8"> in the boilerplate lesson.

HTML character encoding tells the browser how to turn the bytes in your file into letters. Learn what a charset is, why UTF-8 is the right choice, and how to fix broken characters.

You met <meta charset="utf-8"> in the boilerplate lesson. Now let's understand what it really does, what the older charsets were, and what to do when your page shows strange symbols like café instead of café.

What is a character set (charset)?

Computers only store numbers. To store text, they need a table that says "this number means this letter". That table is a character set, or charset. The way those numbers are written as bytes in a file is called the character encoding. In everyday web work people use both words for the same thing.

Think of it like a secret code between two friends. If you write a message with code A and your friend reads it with code B, the message turns into nonsense. The same happens when your editor saves a file in one encoding and the browser reads it in another.

ASCII, ANSI, ISO-8859-1 and UTF-8 compared

Charsets grew over time as computers spread around the world.

ASCII (1960s) has only 128 characters: the English letters A to Z and a to z, the digits 0 to 9, common punctuation, and some invisible control codes. There is no é, no ñ, no €, and nothing for Arabic, Chinese or Hindi.

ISO-8859-1 (also called Latin-1) extended ASCII to 256 characters. It added letters for Western European languages, like é, ü and ñ. It was the default charset of HTML 4. Other ISO-8859 versions covered other regions, such as ISO-8859-5 for Cyrillic, but each page could use only one of them.

ANSI is the name Windows uses for its own older charsets. On English and Western European Windows it usually means Windows-1252, which is almost the same as ISO-8859-1 but adds characters like € and curly quotes. Modern browsers actually treat a page labelled ISO-8859-1 as Windows-1252.

UTF-8 is part of Unicode, one giant list that gives a number to every character in every language, plus symbols and emoji. That is more than 150,000 characters. UTF-8 stores each character in 1 to 4 bytes. The first 128 characters are exactly the same as ASCII, so plain English text looks identical in both.

CharsetCharactersLanguagesUse today?
ASCII128English onlyOnly as part of UTF-8
ISO-8859-1 (Latin-1)256Western EuropeanNo, legacy only
Windows-1252 ("ANSI")256Western EuropeanNo, legacy only
UTF-8All of UnicodeEvery language, symbols, emojiYes, always

UTF-8 is used by about 98% of all websites, and the HTML standard says new pages must use UTF-8.

How to declare UTF-8 with meta charset

Put this line as the very first element inside <head>:

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Café Olé: Menú del día</title>
</head>
<body>
  <h1>Menú del día</h1>
  <p>Paella valenciana: 14 €</p>
  <p>Crème brûlée: 6 € 🍮</p>
</body>
</html>

When this file is saved as UTF-8, the browser shows every accent, the euro sign and the emoji correctly.

Why first? The browser reads the file from the top. It must know the charset before it reads any text, including the <title>. The rule is that the <meta charset> must appear in the first 1024 bytes of the file. Putting it first in the head is the simple way to be safe.

A few more details:

  • utf-8 and UTF-8 both work. Lowercase matches the rest of your HTML.
  • You may see the old long form <meta http-equiv="Content-Type" content="text/html; charset=utf-8">. It still works, but the short form is all you need.
  • The web server can also send the charset in an HTTP header: Content-Type: text/html; charset=utf-8. If the header and the meta tag disagree, the header wins. More about that below.

Common mistake: Writing <meta charset="utf-8"> but saving the file in a different encoding. The meta tag is only a label. It does not change the file. The label and the real encoding must match.

Mojibake: why do I see strange characters on my page?

Mojibake (a Japanese word meaning roughly "character transformation") is the jumble of wrong symbols you get when text is read with the wrong charset. Here are the two most common cases:

What you seeWhat happenedFix
café, €, don’tThe file is UTF-8, but the browser read it as Windows-1252Add <meta charset="utf-8"> or fix the server header
caf� (a black diamond with a question mark)The file is Windows-1252, but the browser read it as UTF-8Re-save the file as UTF-8

In the first case, é is stored as two bytes in UTF-8. The browser thinks each byte is its own Windows-1252 letter, so you see two characters: é. In the second case, the single Windows-1252 byte for é is not valid UTF-8, so the browser shows the replacement character �.

Follow these steps to fix mojibake:

  1. Check that <meta charset="utf-8"> is the first thing in <head>.
  2. Check the real encoding of the file in your editor (see the next section).
  3. Check the server. In the browser, open DevTools (F12), go to the Network tab, click your page and look at the Content-Type response header. It should say charset=utf-8.
  4. If text comes from a database (for example MySQL in XAMPP), make sure the database and connection use utf8mb4, the full UTF-8 that also supports emoji.

If you build pages with PHP, you can send the right header yourself at the very top of the file:

<?php
header('Content-Type: text/html; charset=utf-8');
?>
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Réservation</title>
</head>
<body>
  <p>Merci ! Votre réservation est confirmée.</p>
</body>
</html>

The header() line makes the server tell the browser "this is UTF-8" before any HTML arrives, so the header and the meta tag agree.

How to save a file as UTF-8 in VS Code

VS Code saves new files as UTF-8 by default, but files you copy from older projects or other programs may use another encoding.

  1. Open the file and look at the status bar at the bottom right. You will see the encoding, for example UTF-8 or Windows 1252.
  2. If the text already looks broken in the editor, click the encoding name and choose Reopen with Encoding, then pick the encoding that makes the text look right (often Windows 1252).
  3. Now click the encoding name again, choose Save with Encoding, and pick UTF-8.
  4. Reload the page in the browser.

You may also see UTF-8 with BOM. The BOM (byte order mark) is an invisible marker at the start of the file. It is allowed, but it can cause problems in PHP files (it is sent before your header() calls). Choose plain UTF-8.

Tip: To make sure VS Code never uses anything else, open Settings, search for files.encoding, and check that it is set to utf8.

Practice exercise

Create a file called greetings.html with <meta charset="utf-8"> and a list of "hello" in five languages, for example: Hola, Bonjour, Grüß Gott, Привет, こんにちは, and one emoji. Save it as UTF-8 and open it in the browser. Then use VS Code's Save with Encoding to save it as Windows 1252 and reload the page. Watch what happens, then save it as UTF-8 again to fix it.

Hint: When you save as Windows 1252, VS Code may warn that some characters can't be saved. That is exactly the point: Windows-1252 has no Russian or Japanese letters.

Key takeaways

  • A charset is a table that maps numbers to characters; the encoding is how those numbers are stored as bytes.
  • ASCII, ISO-8859-1 and Windows-1252 ("ANSI") are old and limited. UTF-8 covers every language and emoji.
  • Put <meta charset="utf-8"> first in <head>, and make sure the file is really saved as UTF-8.
  • café means UTF-8 read as Windows-1252; caf� means Windows-1252 read as UTF-8.
  • Check the editor's status bar, the server's Content-Type header and the database charset when fixing mojibake.