Node properties & contents
Read DOM node types, names, text, HTML, and visibility safely with nodeType, tagName, innerHTML, outerHTML, textContent, innerText, and hidden.
- 01Tell what kind of node you haveRead
nodeType,nodeName,tagName, andlocalName, and find the words inside a text node. - 02Pick the right content propertyKnow when to use
textContent,innerText,innerHTML, orouterHTML. - 03Show user text safelyPut text from users on the page as plain text, and explain how an HTML string can run code (XSS).
Questions you can ask a node
The browser reads your HTML and builds a tree of objects called the DOM. Each object in the tree is a node. Some nodes are elements, like a heading or a paragraph. Some are text, the words between tags. Some are comments.
Once you have found a node, you can ask it questions through its properties. A property is a named value on an object, like node.nodeType. This lesson covers three groups:
- What kind of node is this?
nodeType,nodeName,tagName, andlocalName. - What is inside it?
textContent,innerText,innerHTML, andouterHTML. You can read these, and you can also set them to replace what is inside. - Should it be hidden?
hidden.
Node properties answer three questions: "What is this node?", "What text or HTML is inside it?", and "Should this element be hidden?"
Picture a display box in a museum. You can read every note inside it, even notes tucked out of sight. You can read only what a visitor sees through the glass. You can hand over a written plan and have the contents rebuilt. Or you can swap the whole box for a new one.
- In real life: Reading every note inside the box
- In JavaScript:
textContentgives the words of every text node, even hidden ones - In real life: Reading only what a visitor can see
- In JavaScript:
innerTextgives the words shown on screen - In real life: Handing over a written plan for new contents
- In JavaScript:
innerHTML = "<b>bold</b>"throws away the old children and builds new ones from the plan - In real life: Swapping the whole box for a new one
- In JavaScript:
outerHTML = "<section>..."replaces the element itself
Start with a small step-through. The code uses a plain object, card, that holds three strings. Each string is what one real content property would give back for the same paragraph. Pick a property, then step through to see which string comes out.
Pick one property, then step through the read. Each string is what that property gives back for the same paragraph. The experiments further down use real page nodes.
script
textContent: "Hello hidden note", innerHTML: "Hello <span hidden>hidden note</span>", outerHTML: "<p>Hello <span hidden>hidden note</span></p>",};const picked = card.textContent;console.log(picked);nodeType, nodeName, tagName, and localName
INTERACTIVEThe first question is "what kind of node is this?". node.nodeType answers with a number. Each kind of node has its own number. The table shows the common kinds.
Read each row as one kind of node. The columns show what each property gives back for that kind.
| Kind of node | nodeType | nodeName | tagName | localName | Where its contents are |
|---|---|---|---|---|---|
| Element | 1 | The tag name in capitals, like DIV. | The same as nodeName. | The tag name in small letters, like div. | In textContent, or in properties for that element. |
| Text | 3 | #text | None. Reading it gives undefined. | None. Reading it gives undefined. | In nodeValue or data. |
| Comment | 8 | #comment | None. Reading it gives undefined. | None. Reading it gives undefined. | In nodeValue or data. |
| Document | 9 | #document | None. Reading it gives undefined. | None. Reading it gives undefined. | Its textContent is null. |
| Doctype | 10 | The doctype name, usually html. | None. Reading it gives undefined. | None. Reading it gives undefined. | In doctype properties such as name. |
| Fragment | 11 | #document-fragment | None. Reading it gives undefined. | None. Reading it gives undefined. | In its children and textContent. |
An element has three name properties. Here they are on one heading.
const title = document.querySelector("h2");
console.log(title.nodeType);
console.log(title.nodeName, title.tagName, title.localName);- Line 1 finds the first
h2heading on the page. - Line 2 prints
1, the number for an element. - Line 3 prints
H2 H2 h2.nodeNameandtagNameuse capital letters.localNameuses small letters, the way you usually type the tag.
That rule is for HTML elements in an HTML page. SVG elements are different. SVG is the drawing language for shapes inside a page. An SVG circle gives circle in small letters for both tagName and localName. You can check this in the inspector below.
Every element also belongs to a chain of browser classes. A class is a blueprint that says which properties and methods an object gets. For an input box, the chain is:
EventTarget: it can listen for events, like clicks.Node: it has node properties, likenodeType.Element: it has element properties, liketagName.HTMLElement: it has properties every HTML element shares, likehidden.HTMLInputElement: it has input properties, likevalue.
Each class adds to the one before it. So one input is all five at once.
const input = document.querySelector("#lesson-input");
console.log(input instanceof EventTarget);
console.log(input instanceof Node);
console.log(input instanceof Element);
console.log(input instanceof HTMLElement);
console.log(input instanceof HTMLInputElement);Line 1 finds the input. instanceof asks "was this object made from this class, or from a class built on it?". Lines 2 to 6 ask that for each class in the chain. Every line prints true. This example needs a browser. Node.js does not have these DOM classes on its own.
Text and comment nodes are not elements. They keep their characters in a different place.
const text = document.createTextNode("tea");
const comment = document.createComment("later");
console.log(text.nodeValue, text.data);
console.log(comment.nodeName, comment.data);- Line 1 makes a text node that holds
tea. - Line 2 makes a comment node that holds
later. - Line 3 prints
tea tea.nodeValueanddataboth hold the text. - Line 4 prints
#comment later: the comment'snodeName, then itsdata.
tagName. The text node keeps its words in data.const node = event.target;console.log(node.nodeType);console.log(node.nodeName);console.log(node.tagName);console.log(node.localName);console.log(node.nodeValue);nodeType numbers1 element · 3 text · 8 comment · 9 document · 10 doctype · 11 fragment
- nodeType
- —
- nodeName
—- tagName
—- localName
—- nodeValue / data
—
Click the heading, the paragraph, the bold word, the SVG badge, or the card around them. The panel shows each property of the node you clicked. A click always lands on an element, so text and comment nodes can't be clicked here; the table above lists their values. In the SVG badge, tagName is in small letters.
A good habit: check that you have an element before you read an element-only property. tagName exists only on elements. A text node has the nodeName #text, and keeps its words in nodeValue or data.
innerHTML and outerHTML rebuild from a string
INTERACTIVEinnerHTML holds the HTML inside an element, written as a string. HTML written as text like this is called markup.
- When you read
innerHTML, the browser writes the element's children out as a markup string. - When you set
innerHTML, the browser parses your string. To parse means to read the markup and build real nodes from it.
Setting innerHTML is not a small edit. The browser throws away all the old children and builds new ones from your string. Everything attached to the old children is lost:
- Listeners: functions that run when you click or type.
- Focus: which box the cursor was in.
- Variables in your code that pointed at the old children.
- Text a person typed into an input.
innerHTML does not edit the old children. The browser throws them away and builds new ones from the string.You hand a builder a plan: "make the room hold this furniture." The builder does not keep the old chair. They carry everything out, then bring in new furniture that matches the plan.
- In real life: Handing a builder a new plan for the room
- In JavaScript:
box.innerHTML = "<b>bold</b> text" - In real life: The builder carries out all the old furniture first
- In JavaScript: The old child nodes are thrown away
- In real life: Your note about the old chair still exists
- In JavaScript: A variable can still point at an old node
- In real life: But the chair is no longer in the room
- In JavaScript:
oldChild.isConnectedbecomesfalse
const box = document.querySelector("#box");box.innerHTML = "<b>bold</b> text";box.innerHTML += "<em> rebuilt</em>";const old = box.querySelector("b");old.outerHTML = "<strong>new</strong>";console.log(old.isConnected);outerHTML?not replaced yetType in the input box, then try innerHTML +=.
outerHTML goes one step further. It holds the element itself plus its children. Setting it replaces the whole element, not only what is inside.
const old = document.querySelector("#outer-old");
old.outerHTML = "<p id='outer-new'>New text</p>";
console.log(old.isConnected);
console.log(document.querySelector("#outer-new").textContent);- Line 1 finds the old element and keeps it in
old. - Line 2 replaces that element on the page with a new paragraph.
- Line 3 prints
false.isConnectedsays whether a node is still on the page.oldstill points at the removed element. - Line 4 finds the new paragraph and prints
New text.
So after setting outerHTML, search the page again to get the new element. A node that is no longer on the page is called detached.
Two edge cases, checked in a real browser for this lesson:
- Setting
outerHTMLondocument.documentElement(thehtmlelement) throws aNoModificationAllowedError. Its parent is the document itself, which cannot take a replacement. - Setting
outerHTMLon an element that is not on the page does not throw. It also does nothing, because there is no parent to put the replacement in.
A script tag added with innerHTML does not run. That does not make innerHTML safe for text from users. Attributes like onerror hold code, and that code can still run.
textContent vs innerText
INTERACTIVEtextContent reads the words stored in the nodes. It joins the text of every text node inside the element, in order:
- It includes text in hidden elements.
- It includes the text inside
styleandscriptelements. - Setting it replaces all the children with one text node. Characters like
<b>are shown as they are. They do not become tags.
innerText reads the words a person sees on screen. It follows the CSS:
- It skips anything with
display: none. - It applies CSS changes such as
text-transform: uppercase. - It puts line breaks between blocks, like paragraphs, and for each
<br>.
To know what is shown, innerText asks the browser to work out the page layout: where every box sits on screen. That can take extra work. If the element is not shown at all, innerText gives the same value as textContent.
const panel = document.querySelector("#panel");console.log(panel.textContent);console.log(panel.innerText);""innerText
""
textContent gives the text of every text node, so it includes the hidden note and the CSS inside the style element. innerText gives what a reader sees: the hidden note is gone, the CSS uppercase is applied, and blocks get line breaks.
innerText depends on how the page is drawn.const panel = document.querySelector("#panel");
console.log(panel.textContent);
console.log(panel.innerText);- Line 1 finds the panel. It holds
Hello, a hiddenhidden note, and a paragraph,soft words. - Line 2 prints
Hello hidden notesoft words. The hidden note is included. - Line 3 prints
Hello, a blank line, thensoft words. The hidden note is left out.
textContent joins the words of every text node, even hidden ones. innerText gives only the words a reader can see, with line breaks between blocks.Use innerText only when you need to know what is shown. For everything else, use textContent.
Read each row as one property. The columns say what it does when you read it, when you set it, and when to use it.
| Property | When you read it | When you set it | Use it for |
|---|---|---|---|
textContent | It gives the text of every text node inside. | It replaces the children with one text node. | Showing text quickly and safely. |
innerText | It gives only the text shown on screen. | It replaces the children with text, and turns line breaks into <br>. | Checking what a person can see. |
innerHTML | It gives the children written as markup. | It parses the string and builds new children. | Markup you wrote yourself, never user text. |
outerHTML | It gives the element and its children as markup. | It parses the string and replaces the whole element. | Rare cases where the whole element must change. |
One odd case: document.textContent is null. The document is the root of the whole tree, not an element, so it does not join up text the way an element does.
Text from users and XSS
SAFE SANDBOXSome text comes from outside your code: a comment, a profile name, a search word, or a chat message. This is called untrusted text, because anyone could have typed it.
Cross-site scripting, or XSS, is an attack. Someone types markup that holds code. If your page puts that text into innerHTML, the browser builds real elements from it, and the code can run in your page.
userName string, two ways in. innerHTML builds a real element whose code can run. textContent only shows the characters.You can write a visitor's name on your shop sign yourself. That is safe: you only copy the letters. Handing the visitor the pen is different. They can write anything, including orders to your staff.
- In real life: A visitor tells you their name
- In JavaScript: Text a user typed
- In real life: You write the name on the sign yourself
- In JavaScript:
textContent = userName - In real life: The visitor gets the pen and writes anything they like
- In JavaScript:
innerHTML = userName
nameSlot.innerHTML = userName; // unsafe for text from usersnameSlot.textContent = userName; // safe: shows the characters<img src="data:," onerror="parent.postMessage('ran', '*')">No message came back, so no code ran. With textContent, the tag is shown as plain characters and no image is built.
textContentinnerTextinnerHTMLouterHTMLinsertAdjacentHTMLcreateTextNode
Sort each tool by whether it treats a string as plain text or as HTML.
Where you will use this
These properties show up in small, everyday jobs:
- A search result title from your server goes into
textContent. - Markup for an icon comes from your own code, never from user text.
- A "show details" button can set
panel.hiddenand the button'saria-expandedattribute together.aria-expandedtells screen readers whether the panel is open, so every user hears the same thing they see.
function showMessage(slot, message) {
slot.textContent = message;
}
function toggleDetails(button, panel) {
const willOpen = panel.hidden;
panel.hidden = !willOpen;
button.setAttribute("aria-expanded", String(willOpen));
}- Line 2 shows the message as text, so any tags in it stay as plain characters.
- Line 6 notes whether the panel is hidden now. If it is, the click will open it.
- Line 7 flips
hidden. - Line 8 sets
aria-expandedto"true"or"false"to match.
const message = document.querySelector("#message");
message.textContent = "<b>Tea is ready</b>";
console.log(message.textContent);
console.log(message.firstChild.nodeType);- Line 1 finds the message box.
- Line 2 sets its
textContentto a string that looks like a tag. - Line 3 prints the string as it is:
<b>Tea is ready</b>. - Line 4 prints
3. The box has one child, and it is a text node, not a realbelement.
Use the "what kind of node" properties to write careful code. If a function might get a text node, a comment, or an element, check node.nodeType before you read tagName.
Common misconceptions
- "Every node has
tagName." Only elements have it. Text, comment, document, doctype, and fragment nodes don't. UsenodeName, or checknodeTypefirst. - "
innerHTML +=just adds to the end." It still setsinnerHTML. The browser reads the old markup, adds your string, and rebuilds every child. - "Script tags don't run, so
innerHTMLis safe." Attributes likeonerrorcan still run code. Never put untrusted text intoinnerHTML. - "
innerTextandtextContentare the same."innerTextgives only the text shown on screen.textContentgives the text of every text node. - "
hiddenalways hides." The hiding comes from a CSS rule. A stronger rule in your own CSS can beat it. - "After
outerHTML, my variable points at the new element." It still points at the old, removed element. Search the page again to get the new one.
Practice exercises
5 EXERCISESPredict the three lines the console prints.
const node = { nodeType: 8, nodeName: "#comment", data: "todo" };
console.log(node.nodeType);
console.log(node.nodeName);
console.log(node.data);The object copies a comment node. So it prints 8, then #comment, then its data, todo.
A profile name came from a form. Which property should you use to show it?
const userName = "<img src=x onerror=alert(1)>";
console.log(userName);const name = "<img src=x onerror=alert(1)>";
const slot = document.querySelector("#name");
slot.textContent = name;textContent treats the whole string as characters. The page shows the image tag as text and does not build an image.
Write a function for the browser console that describes any node, and does not break on text nodes.
function describe(node) {
// Return type, name, tag, and text data when relevant.
}function describe(node) {
return {
type: node.nodeType,
name: node.nodeName,
tag: node.tagName ?? "not an element",
text: node.nodeType === Node.TEXT_NODE ? node.data : null,
};
}The function reports the kind and name of any node. It reads data only when the node really is a text node.
You replace an element by setting its outerHTML. Is the old variable still on the page?
The old variable still points at the removed element, so old.isConnected is false. Search the page again to find the new one.
What selector would you add to your stylesheet so hidden elements always stay hidden?
.panel[hidden] {
display: flex;
}[hidden] {
display: none !important;
}A [hidden] rule with !important beats other display rules, so hidden elements are hidden again.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which
nodeTypenumber means an element node?Choose an answer to see the explanation.
Question 2 of 7This object copies the names of a paragraph element in an HTML page. What does it print?
Read the code, then predictconst p = { tagName: "P", localName: "p" }; console.log(p.tagName); console.log(p.localName);Choose an answer to see the explanation.
Question 3 of 7Which property includes hidden text, and the text inside
<style>or<script>?Choose an answer to see the explanation.
Question 4 of 7This object copies an old element after
outerHTMLreplaced it. What does it print?Read the code, then predictconst old = { isConnected: false }; console.log(old.isConnected);Choose an answer to see the explanation.
Question 5 of 7Why is
element.innerHTML += morea problem for the children already there?Choose an answer to see the explanation.
Question 6 of 7Which is safest for showing a name a user typed?
Choose an answer to see the explanation.
Question 7 of 7What is the
hiddenproperty linked to?Choose an answer to see the explanation.
Key takeaways
nodeTypetells you the kind of node: element 1, text 3, comment 8, document 9, doctype 10, fragment 11.tagNameexists only on elements.nodeNameworks on every node and gives names like#text.innerHTMLandouterHTMLparse a string and build new nodes. Use them only for markup you wrote, never for user text.textContentgives the text of every text node.innerTextgives only the text shown, and makes the browser work out the layout.hiddenadds or removes an attribute. CSS decides whether that attribute really hides the element.
Final definition: Node properties let you read and change what kind of node you have, the text and HTML inside it, and whether it is hidden.
Up next: Attributes & properties.