The HTML head tag: what goes in it

The head holds information about the page, not content on it. Nothing inside it is drawn, but it decides the tab title, the text encoding, the phone layout and when styles and scripts load.

The <head> element holds information about the page: the text encoding, the title in the tab, the phone layout setting, the search description, and the stylesheets and scripts to load. Nothing inside it is displayed. It sits between <html> and <body>, once per page.

Try it first. This page reads its own head with JavaScript and lists every element in it. The buttons change the title and the theme colour while the page is open.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Head inspector</title>
<meta name="description" content="A page that reads its own head.">
<meta name="theme-color" content="#2563eb">
<meta property="og:title" content="Head inspector">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='7' fill='%232563eb'/%3E%3C/svg%3E">
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
  button { font: inherit; font-size: 14px; padding: 7px 12px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; cursor: pointer; }
  .now { background: #fff; border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; font-size: 14px; line-height: 1.6; }
  .swatch { display: inline-block; width: 14px; height: 14px; border-radius: 4px; vertical-align: -2px; }
  ol { background: #fff; border-radius: 10px; margin: 0; padding: 10px 12px 10px 34px; font: 12.5px/1.6 ui-monospace, Consolas, monospace; }
  li { overflow-wrap: anywhere; }
  li span { color: #6b7280; }
</style>
<script>
  // a script in the head runs before the body exists
  document.documentElement.classList.add('js');
</script>
</head>
<body>
<div class="bar">
  <button id="rename">Change title</button>
  <button id="recolor">Change theme-color</button>
</div>
<div class="now">
  <b>document.title:</b> <span id="title"></span><br>
  <b>theme-color:</b> <span class="swatch" id="swatch"></span> <span id="color"></span>
</div>
<ol id="list"></ol>

<script>
  const meta = document.querySelector('meta[name="theme-color"]');
  const list = document.getElementById('list');

  // One line of text for each kind of head element
  function describe(el) {
    switch (el.tagName) {
      case 'META': return el.hasAttribute('charset') ? 'charset=' + el.getAttribute('charset')
        : (el.name || el.getAttribute('property')) + ' = ' + el.content;
      case 'TITLE': return el.textContent;
      case 'LINK': return 'rel=' + el.rel;
      case 'STYLE': return el.textContent.length + ' characters of CSS';
      case 'SCRIPT': return el.src ? 'src=' + el.src : 'inline script';
      default: return '';
    }
  }

  function render() {
    document.getElementById('title').textContent = document.title;
    document.getElementById('color').textContent = meta.content;
    document.getElementById('swatch').style.background = meta.content;
    list.textContent = '';
    for (const el of document.head.children) {  // everything inside <head>, in order
      const li = document.createElement('li');
      li.innerHTML = '<b></b> <span></span>';
      li.firstChild.textContent = '<' + el.tagName.toLowerCase() + '>';
      li.lastChild.textContent = describe(el);
      list.append(li);
    }
  }

  let n = 1;
  document.getElementById('rename').addEventListener('click', () => {
    document.title = 'Renamed ' + n++;  // rewrites the text of the <title> element
    render();
  });

  const colors = ['#2563eb', '#16a34a', '#ea580c', '#9333ea'];
  document.getElementById('recolor').addEventListener('click', () => {
    meta.content = colors[(colors.indexOf(meta.content) + 1) % colors.length];
    render();
  });

  render();
</script>
</body>
</html>
A page that lists its own head. document.title and the theme-color meta update when you press the buttons.

document.title reads and writes the text of the <title> element. Any meta tag can be changed the same way, by setting its content. In browsers that use theme-color, such as some phone browsers, the address bar colour follows the change.

What goes in the head

These are the elements the head is meant for. Everything else, headings, paragraphs, images, buttons, belongs in the body.

Element What it does
<meta charset="utf-8"> Tells the browser how to turn the file's bytes into characters
<meta name="viewport"> Makes phones lay out the page at the screen's real width
<title> The text in the tab, bookmarks and search results
<meta name="description"> A summary search engines may show under the title
<meta property="og:..."> The title, text and image of a link preview in chat apps
<link rel="stylesheet"> Loads a CSS file
<link rel="icon"> The small icon in the tab
<style> CSS written directly in the page
<script> JavaScript, inline or from a file
<base> Sets the starting address for every relative link on the page

The individual tags each have their own page here. The HTML meta tags list covers which meta tags still do something, and Open Graph tags covers link previews. This page is about the head as a container: what it accepts, and in what order.

<base> is the one to use carefully. Only the first <base href> counts, and it changes where every relative link, image and stylesheet on the page points. Leave it out unless you need it.

The order that matters

Most head elements can appear in any order. Three positions matter.

A head in the order that avoids problems. The green lines are the ones where position matters.
A head in the order that avoids problems. The green lines are the ones where position matters.
  1. Charset first. The HTML standard requires <meta charset> to sit inside the first 1024 bytes of the file. Put it on the first line of the head and it always does.
  2. Styles before scripts. A script that measures elements or reads computed styles needs the CSS applied first. Browsers make a script wait for stylesheets listed above it, so a script below the CSS sees the finished styles.
  3. Scripts with defer. A plain <script src> in the head stops the parser until the file downloads and runs. With defer, the file downloads in the background and runs after the whole page is parsed, in the order written.

defer only works on scripts with a src. On an inline <script> it is ignored, and the code runs straight away, before the body exists. That is why the inline script in the first example is at the end of the body.

head vs header

The names are close, and they are often mixed up. They are unrelated elements.

head is information for the browser. header is a visible block inside the body.
head is information for the browser. header is a visible block inside the body.
  • <head> comes before <body>, appears once, and is never drawn.
  • <header> goes inside <body>. It is a visible block, usually a logo and a menu. A page can have several, for example one per article.

This page has both. The dark bar is its <header>. The grey box prints what the browser put in its <head>.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Head vs header</title>
<meta name="description" content="The head is for the browser. The header is for people.">
<style>
  body { margin: 0; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  header { background: #1d2330; color: #fff; padding: 14px 16px; display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
  header b { font-size: 17px; }
  header nav a { color: #cbd5e1; margin-left: 12px; font-size: 14px; text-decoration: none; }
  main { padding: 14px 16px; }
  .panel { background: #fff; border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; font-size: 14px; line-height: 1.5; }
  .panel h3 { margin: 0 0 6px; font-size: 14px; }
  pre { margin: 0; font: 12px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; color: #374151; }
  .warn { border-left: 4px solid #ea580c; }
</style>
<!-- Deliberately wrong: a visible element written inside head -->
<p id="stray">This paragraph was written inside &lt;head&gt;.</p>
</head>
<body>
<header>
  <b>My site</b>
  <nav><a href="#">Home</a><a href="#">About</a></nav>
</header>
<main>
  <div class="panel">
    <h3>Above: &lt;header&gt;</h3>
    A visible bar inside &lt;body&gt;, usually the logo and menu.
  </div>
  <div class="panel">
    <h3>Not shown: &lt;head&gt;</h3>
    <pre id="head"></pre>
  </div>
  <div class="panel warn">
    <h3>The stray paragraph</h3>
    <span id="where"></span>
  </div>
</main>

<script>
  // Print what the browser actually put inside <head>
  document.getElementById('head').textContent = [...document.head.children]
    .map((el) => el.outerHTML.replace(/>[^<]{60,}</, '>...<'))  // shorten the CSS
    .join('\n');

  // Where did the <p> from the head end up?
  const p = document.getElementById('stray');
  document.getElementById('where').textContent =
    'Its parent is now <' + p.parentElement.tagName.toLowerCase() + '>. ' +
    'The browser closed <head> at the <p> and moved it to the body (it is the first line of this page).';
</script>
</body>
</html>
A visible header, the invisible head printed as text, and a paragraph that was written inside the head on purpose.

The example also shows a typical head mistake. A <p> was written inside the head.

The browser did not show it in the head, because the head cannot hold content. It closed the head there and moved the paragraph into the body, which is why that sentence appears above the dark bar.

What the browser does when the head is missing or wrong

The HTML parser always builds a complete document. If you leave out <html>, <head> or <body>, it creates them. A file that starts with <title> still ends up with a head containing that title.

Left: the source. Right: the element tree the browser builds from it.
Left: the source. Right: the element tree the browser builds from it.

The same repair explains the stray paragraph. When the parser meets an element that belongs in the body, the head ends. Every element after that point, including meta tags and links you meant for the head, is placed in the body.

Open DevTools and look at the Elements panel to see the tree the browser built, not the one you wrote.

The DOCTYPE line above the head is a separate matter. It picks the rendering mode, and the head does not replace it.

A minimal complete head template

A finished head for a single page needs about ten lines. Fill in the form and copy the result.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Head builder</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  form { background: #fff; border-radius: 10px; padding: 12px; display: grid; gap: 10px; margin-bottom: 10px; }
  label { display: grid; gap: 4px; font-size: 13px; font-weight: 600; }
  input[type=text], textarea { font: inherit; font-size: 14px; padding: 7px 9px; border: 1px solid #c9ced8; border-radius: 7px; }
  .row { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; }
  .row label { display: flex; align-items: center; gap: 8px; }
  input[type=color] { width: 44px; height: 30px; border: 0; padding: 0; background: none; }
  .count { font-weight: 400; color: #6b7280; }
  .out { position: relative; }
  pre { margin: 0; background: #1d2330; color: #e5e7eb; border-radius: 10px; padding: 12px; font: 12px/1.55 ui-monospace, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
  #copy { position: absolute; top: 8px; right: 8px; font: inherit; font-size: 13px; padding: 5px 10px; border: 0; border-radius: 6px; background: #fff; cursor: pointer; }
</style>
</head>
<body>
<form id="f">
  <label>Title <input type="text" name="title" value="Q3 results, one page"></label>
  <label>Description <span class="count" id="count"></span>
    <textarea name="desc" rows="2">Revenue, costs and the three numbers to watch next quarter.</textarea></label>
  <div class="row">
    <label>Theme colour <input type="color" name="color" value="#2563eb"></label>
    <label><input type="checkbox" name="viewport" checked> Viewport tag (for phones)</label>
  </div>
</form>
<div class="out">
  <pre id="out"></pre>
  <button id="copy" type="button">Copy</button>
</div>

<script>
  const form = document.getElementById('f');
  const out = document.getElementById('out');

  // Escape text so quotes and < in the title cannot break the HTML
  const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');

  function build() {
    const d = new FormData(form);
    const title = esc(d.get('title').trim()), desc = esc(d.get('desc').trim());
    document.getElementById('count').textContent = '(' + d.get('desc').trim().length + ' characters)';
    const lines = [
      '<head>',
      '  <meta charset="utf-8">',
      d.get('viewport') ? '  <meta name="viewport" content="width=device-width, initial-scale=1">' : null,
      '  <title>' + title + '</title>',
      '  <meta name="description" content="' + desc + '">',
      '  <meta name="theme-color" content="' + d.get('color') + '">',
      '  <meta property="og:title" content="' + title + '">',
      '  <meta property="og:description" content="' + desc + '">',
      '  <link rel="icon" href="/favicon.ico">',
      '  <link rel="stylesheet" href="styles.css">',
      '  <script src="app.js" defer><\/script>',
      '</head>',
    ];
    out.textContent = lines.filter(Boolean).join('\n');
  }

  form.addEventListener('input', build);
  form.addEventListener('submit', (e) => e.preventDefault());  // Enter does not reload

  document.getElementById('copy').addEventListener('click', async (e) => {
    try {
      await navigator.clipboard.writeText(out.textContent);
      e.target.textContent = 'Copied';
    } catch {
      getSelection().selectAllChildren(out);  // clipboard blocked: select it for Ctrl+C
      e.target.textContent = 'Selected';
    }
    setTimeout(() => (e.target.textContent = 'Copy'), 1500);
  });

  build();
</script>
</body>
</html>
Type a title and description, pick a colour, and copy the head block it builds.

The template it writes:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Q3 results, one page</title>
  <meta name="description" content="Revenue, costs and the three numbers to watch next quarter.">
  <meta name="theme-color" content="#2563eb">
  <meta property="og:title" content="Q3 results, one page">
  <meta property="og:description" content="Revenue, costs and the three numbers to watch next quarter.">
  <link rel="icon" href="/favicon.ico">
  <link rel="stylesheet" href="styles.css">
  <script src="app.js" defer></script>
</head>
<body>
  ...
</body>
</html>

For a page that has to work as one file, replace the stylesheet link with a <style> block. External stylesheets explains when a separate file is worth it. Writing the description well is its own topic, covered in meta description.

When it does not work

What you see Cause Fix
The tab title does not change Two <title> elements, and you edited the second Keep one title. Browsers use the first
The page shows briefly without styles The stylesheet <link> is at the end of the body Move it into the head
é shows as é No <meta charset>, and the server did not name the encoding either <meta charset="utf-8"> as the first line of the head
A phone shows a tiny desktop-width page No viewport meta tag Add the viewport meta tag
Text written in the head shows on the page Visible elements cannot live in the head Move them into the body
Meta tags stop working after a certain line A body element in the head closed it early Remove the stray element, then check the Elements panel
A script cannot find an element It runs in the head before the body exists Add defer, or move it to the end of the body

Head problems are easiest to judge in the real page: the title in the tab, the layout on a phone. A screenshot shows neither, and an .html attachment may open as plain code on a phone.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can use the page themselves. If you change the code later, the same link shows the new version.

Questions people ask

What goes in the head tag in HTML?

Information about the page: meta charset, the viewport meta tag, one title, meta description and Open Graph tags, link elements for stylesheets and the icon, style blocks, script elements, and optionally a base element. Visible content such as headings, paragraphs and images belongs in the body.

What is the difference between head and header in HTML?

head is the invisible part of the document before body, with the title and meta tags. header is a visible element inside body, usually a logo and menu at the top of the page or of a section. A page has one head but can have several header elements.

Is the head tag required in HTML5?

The tags themselves can be left out. The browser creates the head element anyway and puts the title and meta tags into it. Writing it out is still clearer, and a valid page still needs a title element.

Should script tags go in the head or at the end of the body?

Either works. A script with the defer attribute can sit in the head: it downloads while the page is parsed and runs after parsing finishes. A plain script without defer stops parsing while it loads and runs, which is why it was often placed at the end of the body.

Why does my title not change?

Usually there are two title elements. Browsers use the first one, so editing the second has no effect. Keep exactly one title in the head.

Keep reading

meta charset in HTML: what it does and why it goes firstWhat meta charset="utf-8" does, why it must sit in the first 1024 bytes, and how a BOM or thThe HTML base tag: href, target and what it breaksWhat the HTML base tag does to every relative link, image and #anchor on a page. Live examplThe HTML noscript tag: when it shows and what to put in itThe HTML noscript tag shows its content only when JavaScript is turned off, not when a scripThe HTML style tag: CSS inside the pageThe HTML style tag puts CSS inside the page. Where it goes, why order and specificity decideThe main tag in HTML: what goes inside, and where it goesWhat the HTML main tag is for, the one-per-page rule, where it may not go, main vs div and sHTML meta tags listA working list of HTML meta tags grouped by what they affect: rendering, search results, preThe viewport meta tag: the one line mobile needsWhat the viewport meta tag does: tells a phone to lay the page out at its real width insteadOpen Graph tags: the preview card when you share a linkOpen Graph tags are meta lines in the head that chat apps and social feeds read to draw a prMeta description and title: what a search result actually showsThe title tag and meta description are the two lines a search result shows. What each does, What is the DOCTYPE in HTML?What the DOCTYPE in HTML does: one first line that tells the browser to use current rules inExternal stylesheets: when a separate CSS file helpsAn external stylesheet is a CSS file the page links to. Right for a site of many pages shari