dl, dt and dd in HTML: description lists for names and values

A dl holds pairs: a dt names something and the dd after it describes it. Glossaries, spec sheets and order summaries are all this shape.

A description list is three tags. <dl> wraps the list, <dt> holds a term or name, and each <dd> after it holds the description or value. There are no bullets or numbers. The browser puts each part on its own line and indents the dd.

<dl>
  <dt>Format</dt>
  <dd>HTML page</dd>
  <dt>Authors</dt>
  <dd>Ana Ruiz</dd>
  <dd>Ben Cho</dd>
</dl>

Switch between the default look and two grid versions below. The dashed outlines show where each dt and dd box sits.

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>dl: default vs grid</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; color: #1d2330; background: #f4f5f7; }
  .modes { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
  .modes button {
    font: inherit; font-size: 13px; padding: 7px 11px; border-radius: 8px;
    border: 1px solid #c9cdd4; background: #fff; cursor: pointer;
  }
  .modes button[aria-pressed="true"] { background: #1d2330; border-color: #1d2330; color: #fff; }
  .box { background: #fff; border-radius: 10px; padding: 4px 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, .08); }
  dt { font-weight: 600; }
  /* the dt/dd outlines only make the boxes visible */
  dt, dd { outline: 1px dashed #c9cdd4; outline-offset: 1px; }

  /* 1. two columns, nothing else */
  dl.grid { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; }
  dl.grid dd { margin: 0; }
  /* 2. pin every dt to column 1 and every dd to column 2 */
  dl.fixed dt { grid-column: 1; }
  dl.fixed dd { grid-column: 2; }

  pre {
    margin: 12px 0 0; padding: 10px 12px; border-radius: 8px; background: #1d2330; color: #e6e9ef;
    font: 12.5px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap;
  }
</style>
</head>
<body>
<div class="modes">
  <button data-mode="" aria-pressed="true">Default</button>
  <button data-mode="grid">Grid</button>
  <button data-mode="grid fixed">Grid + grid-column</button>
</div>

<div class="box">
  <dl id="meta">
    <dt>Format</dt>
    <dd>HTML page</dd>
    <dt>Authors</dt>
    <dd>Ana Ruiz</dd>
    <dd>Ben Cho</dd>
    <dt>Updated</dt>
    <dd>26 September 2026</dd>
    <dt>License</dt>
    <dd>CC BY 4.0</dd>
  </dl>
</div>

<pre id="css"></pre>

<script>
  const dl = document.getElementById('meta');
  const css = document.getElementById('css');
  const code = {
    '': '/* no CSS: dd is indented by the browser\'s\n   margin-inline-start: 40px */',
    'grid': 'dl { display: grid;\n     grid-template-columns: max-content 1fr; }\ndd { margin: 0; }\n/* "Ben Cho" drops into the term column */',
    'grid fixed': 'dl { display: grid;\n     grid-template-columns: max-content 1fr; }\ndd { margin: 0; grid-column: 2; }\ndt { grid-column: 1; }',
  };

  document.querySelectorAll('.modes button').forEach((btn) => {
    btn.addEventListener('click', () => {
      document.querySelectorAll('.modes button').forEach((b) => b.setAttribute('aria-pressed', b === btn));
      dl.className = btn.dataset.mode;
      css.textContent = code[btn.dataset.mode];
    });
  });
  css.textContent = code[''];
</script>
</body>
</html>
The same dl three ways. Default, a plain two-column grid, and a grid with each column pinned.

How dl, dt and dd fit together

A dd belongs to the nearest dt above it. There is no closing wrapper around a pair: the next dt starts the next group. That is how "Authors" above owns both names.

dt names something, the dd elements after it describe it, and the browser indents each dd.
dt names something, the dd elements after it describe it, and the browser indents each dd.

A group can take several shapes, and all of them are valid:

Shape Markup Example
One name, one value dt + dd Weight: 250 g
One name, several values dt + dd + dd Authors: two people
Several names, one value dt + dt + dd "Email" and "E-mail" share one rule
Grouped <div> around dt and dd Cards, hiding a group, styling a row

Two rules come from the HTML standard. Within one dl, each name should appear in only one dt. And the standard says a dl is not the right element for dialogue, such as a chat transcript.

Removing the default dd indent

The indent is not padding on the list. It is a default margin on each dd: margin-inline-start: 40px, which is the left margin in a left-to-right page. The dl itself also gets a default top and bottom margin, like a paragraph.

dd { margin: 0; }            /* no indent */
dl { margin: 0; }            /* no space above and below */
dt { font-weight: 600; }     /* make the names stand out */

Only the look changes. The browser still knows which text is the name and which is the value.

A two-column dl with CSS grid

The layout people usually want puts names on the left and values on the right. Setting display: grid on the dl with two columns gets close, and it breaks as soon as one name has two values.

Left: grid places items in order, so an extra dd shifts every later row. Right: pinned columns keep them aligned.
Left: grid places items in order, so an extra dd shifts every later row. Right: pinned columns keep them aligned.

Grid fills cells one after another. The second author lands in column 1, and every name after it shifts to column 2. Pin each element to its column and the browser starts a new row when it needs to:

dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; }
dt { grid-column: 1; }
dd { grid-column: 2; margin: 0; }

max-content makes the first column as wide as the longest name. For long names, use a fixed width such as 10em instead, so a single long label does not squeeze the values. The CSS grid guide covers the column syntax.

Wrapping groups in a div

Current HTML allows a <div> directly inside dl around each group. The div is there for styling and for attributes that apply to the whole group. Inside it go one or more dt elements followed by one or more dd elements.

<dl>
  <div>
    <dt>Sign in</dt>
    <dd>Verb: "Sign in to your account."</dd>
    <dd>Noun: "sign-in".</dd>
  </div>
</dl>

It is all or nothing: once one group has a div, every group in that dl needs one.

With a wrapper, each group becomes a box you can turn into a card, give a border, or hide in one step. Type in the filter below and whole groups disappear together.

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>dl with div groups</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; color: #1d2330; background: #f4f5f7; }
  label { display: block; font-size: 13px; color: #5b6270; margin-bottom: 10px; }
  input {
    display: block; width: 100%; box-sizing: border-box; margin-top: 4px;
    font: inherit; padding: 8px 10px; border: 1px solid #c9cdd4; border-radius: 8px;
  }
  /* the dl is the card grid; each div is one card */
  dl {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 10px; margin: 0;
  }
  dl > div {
    background: #fff; border-radius: 10px; padding: 12px 14px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .08);
  }
  dt { font-weight: 700; }
  dt + dt { color: #5b6270; font-weight: 600; }  /* a second name for the same thing */
  dd { margin: 6px 0 0; font-size: 14px; line-height: 1.45; }
  dd + dd { border-top: 1px solid #eceef1; padding-top: 6px; }
  #count { font-size: 12px; color: #5b6270; margin: 10px 0 0; }
</style>
</head>
<body>
<label>Filter the style guide
  <input id="q" type="search" placeholder="Try: email, verb, log">
</label>

<dl id="guide">
  <div>
    <dt>Email</dt>
    <dt>E-mail</dt>
    <dd>Write it as "email": lower case, no hyphen.</dd>
  </div>
  <div>
    <dt>Sign in</dt>
    <dd>Verb: "Sign in to your account."</dd>
    <dd>Noun or adjective: "sign-in", as in "the sign-in page".</dd>
  </div>
  <div>
    <dt>Log in</dt>
    <dt>Login</dt>
    <dd>Do not use. Say "sign in".</dd>
  </div>
  <div>
    <dt>Set up</dt>
    <dd>Verb: "Set up your team."</dd>
    <dd>Noun: "setup", as in "a quick setup".</dd>
  </div>
</dl>
<p id="count"></p>

<script>
  const q = document.getElementById('q');
  const groups = document.querySelectorAll('#guide > div');
  const count = document.getElementById('count');

  function filter() {
    const text = q.value.trim().toLowerCase();
    let shown = 0;
    groups.forEach((g) => {
      // hiding the div hides the terms and their descriptions together
      const hit = g.textContent.toLowerCase().includes(text);
      g.hidden = !hit;
      if (hit) shown++;
    });
    count.textContent = shown + ' of ' + groups.length + ' groups shown';
  }

  q.addEventListener('input', filter);
  filter();
</script>
</body>
</html>
Each div is one card. Some cards have two names, some have two descriptions. The filter hides whole groups.

What goes inside dt and dd

A dd can hold any flow content: paragraphs, lists, images, links, even another dl. A dt is a little stricter. It can hold flow content such as a paragraph, but no headings, no header or footer, and no sectioning elements such as section or article.

In practice, keep the dt short, a word or a phrase. When the names are abbreviations, the abbr tag can expand them inside the dt, which is a natural fit for a glossary.

dl vs table vs ul

All three can hold a list of facts. Pick by the shape of the data, not by the look you want, because CSS can make each one look like the others.

ul for items, dl for one thing described by name and value, table for several things compared on the same fields.
ul for items, dl for one thing described by name and value, table for several things compared on the same fields.
Element Data shape Good for
<ul> Items without names Features, tags, menus
<dl> Name and value pairs Glossaries, specs, metadata, simple FAQs
<table> Rows and columns Comparing products, schedules, prices

The ul tag guide covers bullets, and the ol tag guide covers numbered steps. A short FAQ also fits a dl: the question in dt, the answer in dd.

Screen readers treat the three differently. Many announce a dl as a list, and some also identify terms and their descriptions. The exact wording depends on the screen reader and browser. A dl built from div and span elements carries none of that meaning.

A finished example: spec sheet and order summary

This page uses two description lists. The spec sheet wraps each pair in a div that is its own two-column grid. The order summary puts the label left and the amount right with flexbox.

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>Spec sheet and order summary</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; color: #1d2330; background: #f4f5f7; }
  .page { display: grid; grid-template-columns: 3fr 2fr; gap: 12px; align-items: start; }
  .card { background: #fff; border-radius: 12px; padding: 14px 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, .08); }
  h2 { font-size: 16px; margin: 0 0 10px; }

  /* spec sheet: label column + value column, one row per div */
  .specs { margin: 0; }
  .specs > div {
    display: grid; grid-template-columns: 8.5em 1fr; gap: 12px;
    padding: 7px 0; border-top: 1px solid #eceef1;
  }
  .specs dt { color: #5b6270; font-size: 14px; }
  .specs dd { margin: 0; font-size: 14px; }
  .specs dd + dd { grid-column: 2; }  /* extra values stay in the value column */

  /* order summary: label left, amount right */
  .sum { margin: 0; }
  .sum > div { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: 14px; }
  .sum dd { margin: 0; font-variant-numeric: tabular-nums; }
  .sum .total { border-top: 1px solid #d5d9e0; margin-top: 6px; padding-top: 9px; font-weight: 700; font-size: 16px; }
  .qty { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 14px; }
  .qty button { width: 30px; height: 30px; border-radius: 8px; border: 1px solid #c9cdd4; background: #fff; font-size: 16px; cursor: pointer; }

  /* phones: one column, and each spec label sits above its value */
  @media (max-width: 520px) {
    .page { grid-template-columns: 1fr; }
    .specs > div { grid-template-columns: 1fr; gap: 2px; }
    .specs dd + dd { grid-column: 1; }
  }
</style>
</head>
<body>
<div class="page">
  <section class="card">
    <h2>Trail 2 headphones</h2>
    <dl class="specs">
      <div><dt>Driver</dt><dd>40 mm dynamic</dd></div>
      <div><dt>Battery</dt><dd>Up to 30 hours</dd></div>
      <div><dt>Connection</dt><dd>Bluetooth 5.3</dd><dd>3.5 mm cable</dd></div>
      <div><dt>Weight</dt><dd>250 g</dd></div>
      <div><dt>Colours</dt><dd>Black</dd><dd>Sand</dd><dd>Sage</dd></div>
      <div><dt>In the box</dt><dd>Headphones, USB-C cable, 3.5 mm cable, pouch</dd></div>
    </dl>
  </section>

  <section class="card">
    <h2>Order summary</h2>
    <div class="qty">Quantity
      <button id="minus" aria-label="One fewer">&minus;</button>
      <b id="n">1</b>
      <button id="plus" aria-label="One more">+</button>
    </div>
    <dl class="sum">
      <div><dt>Subtotal</dt><dd id="sub"></dd></div>
      <div><dt>Shipping</dt><dd id="ship"></dd></div>
      <div class="total"><dt>Total</dt><dd id="total"></dd></div>
    </dl>
  </section>
</div>

<script>
  const PRICE = 89, SHIP = 6, FREE_FROM = 150;  // free shipping from 150
  let n = 1;
  const money = (v) => '$' + v.toFixed(2);

  function render() {
    const sub = PRICE * n;
    const ship = sub >= FREE_FROM ? 0 : SHIP;
    document.getElementById('n').textContent = n;
    document.getElementById('sub').textContent = money(sub);
    document.getElementById('ship').textContent = ship ? money(ship) : 'Free';
    document.getElementById('total').textContent = money(sub + ship);
  }

  document.getElementById('plus').addEventListener('click', () => { n = Math.min(n + 1, 9); render(); });
  document.getElementById('minus').addEventListener('click', () => { n = Math.max(n - 1, 1); render(); });
  render();
</script>
</body>
</html>
Change the quantity to update the summary. On a phone, the cards stack and each spec label sits above its value.
  • Several values per name: "Connection" and "Colours" have more than one dd. dd + dd { grid-column: 2; } keeps the extra values in the value column.
  • Phones: a media query under 520px switches the page to one column and stacks each label above its value.
  • Totals: font-variant-numeric: tabular-nums makes the digits the same width, so amounts line up.

When it does not work

What you see Cause Fix
Values are pushed 40px to the right Default margin-inline-start on dd dd { margin: 0; }
Grid rows shift after one name A name has two dd, and grid fills cells in order dt { grid-column: 1 }, dd { grid-column: 2 }, or div groups
A dd sits next to the wrong name Several dt share one dd and it lines up with the last one Wrap the group in a div and style the div
Validator error on the div Some groups wrapped, some bare, or a div with other content Wrap every group, only dt and dd inside
Validator error on the dt A heading or section inside dt Keep the dt to text or a paragraph
A dl used only to indent text No name and value in the content Use margin or padding on a div or p
Hidden group still shows A display rule on the div overrides hidden Add [hidden] { display: none; }

A spec sheet or a glossary is meant to be read by someone else. A screenshot cannot be searched or copied from, 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 filter and the quantity buttons work for the people you send it to. If you change the code later, the same link shows the new version.

Questions people ask

What do dl, dt and dd stand for?

dl is a description list, dt is the description term and dd is the description details. Older versions of HTML called dl a definition list. The current name is broader: a dl holds any name and value pairs, not only terms and definitions.

Can one dt have more than one dd?

Yes. Every dd that follows a dt belongs to it until the next dt starts. Several dt elements in a row can also share the dd elements that follow them, which suits a term with more than one spelling.

Can I put a div inside a dl?

Yes, as a wrapper around one group: one or more dt elements followed by one or more dd elements. If you use div wrappers, every group in that dl needs one. A dl cannot mix div groups with bare dt and dd elements.

How do I remove the indent on dd?

The indent comes from the browser's default margin-inline-start: 40px on dd. Set dd { margin: 0; } or margin-inline-start: 0 to remove it.

Should I use dl or a table for product specs?

A dl fits one product described by name and value. When several products are compared on the same fields, the data has rows and columns, and a table is the better match.

Keep reading