Character Encoding and the Viewport Meta Tag
Two lines that every page needs. One decides whether your text is readable at all, the other decides whether your page works on a phone.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Character encoding
<meta charset="utf-8">A file arrives as bytes. Encoding is the mapping from those bytes to characters, and the browser has to know it before it can read a single word.
Guess wrong and you get the familiar damage: a rupee sign becoming a row of symbols, an accented letter turning into two nonsense characters, an em dash appearing as three. The text is intact on the server; it is being decoded with the wrong table.
Why UTF-8
UTF-8 encodes every character in Unicode - every script, every symbol, every emoji - while keeping plain ASCII as single bytes, so English text costs nothing extra. It is the encoding of over ninety eight percent of the web and the only correct choice for new work.
It must come first
The browser reads the first bytes looking for the declaration. If it is not found early it either guesses from the content or falls back to a default, and by the time your declaration appears the damage is already done.
<!-- correct: nothing before it -->
<head>
<meta charset="utf-8">
<title>Fee structure - Rs 48,000</title>
</head>
<!-- broken: the title is decoded before the declaration is read -->
<head>
<title>Fee structure - Rs 48,000</title>
<meta charset="utf-8">
</head>Three places encoding is decided
- The HTTP header -
Content-Type: text/html; charset=utf-8. This wins over everything. - The meta tag - the fallback when the header says nothing.
- The file itself - the bytes must actually be UTF-8. Declaring it does not convert anything.
All three have to agree. A file saved as Windows-1252 and declared as UTF-8 is still broken, and this is the usual cause of mojibake that survives every attempt to fix the markup. Set the editor to save as UTF-8 without a byte order mark.
The old syntax
<!-- still valid, unnecessarily long -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!-- use this -->
<meta charset="utf-8">The viewport
<meta name="viewport" content="width=device-width, initial-scale=1">Without this line, a mobile browser assumes the page was designed for a desktop. It pretends the screen is around nine hundred and eighty pixels wide, renders the page at that width, then shrinks the whole thing to fit - producing a page rendered correctly and displayed at about a third of readable size.
Every media query in your stylesheet is evaluated against that fictional width, so responsive layouts never activate. The page is not broken; it is being rendered for a screen that does not exist.
What the values mean
| Value | Effect |
|---|---|
width=device-width | Use the real screen width as the viewport width |
initial-scale=1 | Start at one hundred percent zoom, no shrinking |
minimum-scale | Lower zoom limit |
maximum-scale | Upper zoom limit |
user-scalable | Whether pinch zoom is allowed |
viewport-fit=cover | Extend into the display cutout area on notched devices |
Never disable zoom
<!-- an accessibility failure -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">Zoom is how readers with low vision use the web. Blocking it fails the Web Content Accessibility Guidelines directly, and modern browsers increasingly ignore the restriction anyway. The line above appears in a great many templates and should be deleted wherever it is found.
The usual reason it was added - preventing an unwanted zoom when a form field is focused on iOS - has a proper fix: set form field font size to at least sixteen pixels.
input, select, textarea { font-size: 16px; }Devices with a display cutout
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">body {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
}Other meta tags in this family
<!-- browser interface colour on mobile -->
<meta name="theme-color" content="#1e3a8a">
<!-- a different colour per scheme -->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0f172a">
<!-- which schemes the page supports -->
<meta name="color-scheme" content="light dark">color-scheme is worth adding: it tells the browser to render form controls, scrollbars and default backgrounds in the matching scheme, which removes the white flash a dark themed site otherwise shows on load.
Important rules
- Declare the encoding in the first kilobyte of the document.
- The HTTP header overrides the meta tag.
- The file must actually be saved in the encoding you declare.
- Without a viewport tag, mobile browsers assume a desktop width.
- Never disable user scaling.
- Both tags belong on every page, without exception.
Common mistakes
- No charset declaration, then confusion when symbols break in production but not locally.
- Declaring UTF-8 while saving the file in another encoding.
- Declaring the encoding after content that uses non ASCII characters.
- Omitting the viewport tag and shipping a desktop layout to phones.
- Copying a template containing
user-scalable=no. - Setting form field text below sixteen pixels and getting the iOS zoom, then blocking zoom to fix it.
Best practices
- Make both tags the first two lines of every head.
- Configure the editor and the build to write UTF-8 without a byte order mark.
- Serve the charset in the HTTP header as well.
- Use exactly
width=device-width, initial-scale=1and nothing more unless there is a reason. - Add
color-schemeandtheme-colorwhen the site has a dark mode. - Test on a real phone, not only a resized desktop window.
Practice
- Remove the charset declaration from a page containing accented characters and reload. Then move it below the title and reload again.
- Save a file in a non UTF-8 encoding while declaring UTF-8, and describe what appears.
- Load a responsive page on a phone with the viewport tag removed. Do the media queries fire?
- Add
color-schemeto a dark themed page and note what happens to the scrollbars and form fields.