ESC

Type to search the knowledge base.

DocumentFragment

Build subtrees off-DOM with DocumentFragment — one insert, fewer reflows, and how it differs from a wrapper div.

intermediate3 min read
  • javascript
  • documentfragment
  • dom
  • performance

Inserting nodes one-by-one into a live document can cost a layout/style pass each time. A DocumentFragment is a minimal document-like node that holds children off the live tree. When you insert the fragment, its children move into the target — the fragment itself does not stay as a wrapper.

Basic use

const frag = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  frag.append(li);
}

document.querySelector('#list').append(frag);
// one insertion of 1000 children; frag is now empty
frag.childNodes.length; // 0

Children are moved, not copied. After append, the fragment is empty and reusable.

Why not a temporary div?

const wrap = document.createElement('div');
// ... append lis to wrap
list.append(wrap); // leaves an extra <div> in the DOM
list.append(...wrap.childNodes); // works but live NodeList quirks

A fragment:

  • Does not appear in the document
  • Has no parent
  • Doesn’t affect CSS selectors as a container
  • Is the intended batching primitive
// template content is a DocumentFragment
const tpl = document.querySelector('#row');
const node = tpl.content.cloneNode(true);
list.append(node);

Range and fragment

const range = document.createRange();
range.selectNodeContents(list);
const taken = range.extractContents(); // DocumentFragment of former children
// mutate taken...
list.append(taken);

Useful for reordering or sanitizing a block without destroying identity of nodes you keep.

Performance reality check

Modern engines are fast; batching 20 nodes rarely needs a fragment for FPS. Fragments still win for:

  • Large lists built from scratch
  • Clear code intent (“build then attach”)
  • Avoiding intermediate layout when reading geometry between inserts

If you interleave offsetHeight reads with inserts, a fragment alone won’t save you — stop thrashing.

With components / frameworks

Frameworks already batch DOM commits. You rarely hand-manage fragments inside React. You will still use them in:

  • Vanilla widgets
  • Design-system renderers
  • Markdown/HTML sanitizers that build nodes
  • Interview “render a list efficiently” prompts
function renderOptions(select, options) {
  const frag = document.createDocumentFragment();
  for (const { value, label } of options) {
    const opt = document.createElement('option');
    opt.value = value;
    opt.textContent = label;
    frag.append(opt);
  }
  select.replaceChildren(frag);
}

replaceChildren + fragment is a clean full refresh.

Interview answer

“DocumentFragment is an off-DOM container. I append many children to it, then append the fragment once to a live parent; children move in and the fragment empties. It avoids wrapper elements and reduces incremental layout cost for large inserts. Template.content is also a fragment.”

Measuring after attach

Fragments have no layout until attached — reading geometry on nodes while only in a fragment can yield zeros depending on the node type and browser. Attach first, then measure; or use a hidden live container if you must measure offscreen.

const frag = document.createDocumentFragment();
// build...
parent.append(frag);
// now children are live — measure parent or children
const h = parent.scrollHeight;

querySelector works on a fragment for nodes you put in it (frag.querySelector('.x')), which is handy while building. After append, those nodes are gone from the fragment — hold references if you still need them.

Nested fragments and move semantics

const outer = document.createDocumentFragment();
const inner = document.createDocumentFragment();
inner.append(document.createElement('span'));
outer.append(inner); // moves span into outer; inner empties
parent.append(outer);

Appending a fragment always moves its children. That makes fragments safe builders: compose small fragments into larger ones, then attach once. Don’t keep long-lived references expecting the fragment to still contain nodes after a parent append.

Further reading

Related guides