Lesson 08 / 26
Inheritance and Default Values
How the value of a property no declaration reaches is determined; inherited and non-inherited properties, initial value, and the inherit, initial, unset, and revert keywords.
Contents
The cascade picks the winner among declarations that match an element. One problem
remains: what happens if no declaration matches at all? If no rule writes color to a
cell in the measurement table, what is that cell’s text color?
The gap is filled by two separate mechanisms. Some properties inherit their value from the parent element; the rest fall back to a defined initial value. This lesson separates the two mechanisms, gives the reason behind which property is subject to which one, and defines the keywords that call each of them explicitly.
Inheritance Works Down the Tree
Inheritance is a property’s value passing from parent element to child element down the document tree. Not every property inherits; which property inherits is part of the property’s definition, and it is not arbitrary.
Roughly: properties that determine the appearance of text inherit — color,
font-family, font-size, font-weight, line-height, text-align,
letter-spacing, visibility. Properties that determine the geometry of a box do not
inherit — margin, padding, border, width, height, background, display,
position.
The reason for the distinction is usefulness. Setting a section’s typeface once and having all the text inside it take it on is an ordinary thing to want; giving a section a border and wanting every paragraph inside it to also get a border is not. Inheritance is open in decisions where repetition is ordinary.
The inherited value is the parent element’s computed value, not the text written in
the source. If a parent element has font-size: 150% written on it, what passes to the
child is not the text 150%, but that percentage’s resolved result. This distinction is
handled with numbers in the units lesson.
Initial Value Is Not the Browser Default
If no declaration reaches a non-inherited property, the initial value is used. This value is written into the property’s definition and is independent of implementation.
There is a distinction here that is commonly confused. A div element producing a block
box does not come from the display property’s initial value — the display property’s
initial value is inline. A div element being block is because the browser’s own
default style file has div { display: block } written in it.
The distinction can be summarized as: the initial value comes from the language’s definition, the browser default comes from a style file. The first takes effect not when no rule is written, but when no rule wins; the second is a declaration capable of winning and, as the previous lesson showed, is the weakest source in the cascade.
Running Value Resolution
The following program takes declarations that won the cascade as input and walks down the tree, resolving each element’s value for four properties. Each line ends with where the value came from.
// inheritance.mjs — runs value resolution down the tree const INHERITED = new Set(["color", "font-family", "line-height", "text-align", "visibility"]); const INITIAL = { "color": "canvastext", "font-family": "serif", "line-height": "normal", "text-align": "start", "visibility": "visible", "border-color": "currentcolor", "background-color": "transparent", }; // winning declarations (cascade result) — node path -> {property: value} const winners = { "body": { "color": "#1c2733", "font-family": "system-ui" }, "body/main": {}, "body/main/table": { "text-align": "right", "border-color": "#d5dbe0" }, "body/main/table/tr": {}, "body/main/table/tr/td.name": { "text-align": "start" }, "body/main/table/tr/td.missing":{ "color": "inherit", "text-align": "unset", "border-color": "unset", "font-family": "initial" }, }; const tree = { name: "body", children: [ { name: "main", children: [ { name: "table", children: [ { name: "tr", children: [ { name: "td.name", children: [] }, { name: "td.missing", children: [] } ] }, ] } ] } ] }; const PROPERTIES = ["color", "font-family", "text-align", "border-color"]; function resolve(node, path, parentValues) { const fullPath = path ? `${path}/${node.name}` : node.name; const written = winners[fullPath] ?? {}; const values = {}; for (const prop of PROPERTIES) { let v = written[prop]; let source; if (v === undefined) { // no declaration: take from parent if inherited, else initial value if (INHERITED.has(prop) && parentValues) { v = parentValues[prop]; source = "inherited"; } else { v = INITIAL[prop]; source = "initial"; } } else if (v === "inherit") { v = parentValues ? parentValues[prop] : INITIAL[prop]; source = "inherit"; } else if (v === "initial") { v = INITIAL[prop]; source = "initial-keyword"; } else if (v === "unset") { if (INHERITED.has(prop) && parentValues) { v = parentValues[prop]; source = "unset->inherited"; } else { v = INITIAL[prop]; source = "unset->initial"; } } else { source = "declared"; } values[prop] = v; console.log(` ${prop.padEnd(12)} = ${String(v).padEnd(12)} (${source})`); } for (const c of node.children) { console.log(`${fullPath}/${c.name}`); resolve(c, fullPath, values); } } console.log("body"); resolve(tree, "", null);
body color = #1c2733 (declared) font-family = system-ui (declared) text-align = start (initial) border-color = currentcolor (initial) body/main color = #1c2733 (inherited) font-family = system-ui (inherited) text-align = start (inherited) border-color = currentcolor (initial) body/main/table color = #1c2733 (inherited) font-family = system-ui (inherited) text-align = right (declared) border-color = #d5dbe0 (declared) body/main/table/tr color = #1c2733 (inherited) font-family = system-ui (inherited) text-align = right (inherited) border-color = currentcolor (initial) body/main/table/tr/td.name color = #1c2733 (inherited) font-family = system-ui (inherited) text-align = start (declared) border-color = currentcolor (initial) body/main/table/tr/td.missing color = #1c2733 (inherit) font-family = serif (initial-keyword) text-align = right (unset->inherited) border-color = currentcolor (unset->initial)
Three observations follow from the output.
color and font-family were only declared on the body element, and carried down five
generations to the cells. A declaration written at the root of the tree affects the entire
subtree.
border-color was #d5dbe0 at the table but dropped back to currentcolor at the row.
Border color is not an inherited property; a border color given to the table does not
pass to the cells. This is why setting up a border on a table requires writing a rule to
the cells as well.
text-align became right at the table and inherited down to the row and cells; a
declaration on the td.name element overrode it to start. Inheritance does not hold
once there is a declaration on the child element — inheritance is the weakest source and
is always overridden by a declaration.
Four Keywords
There are four keywords that can be written to any property’s value. The output’s last block shows three of the four.
inherit — forces even a non-inherited property to be taken from the parent element.
color: inherit was written on td.missing, and the value came from above. This is
meaningful on a non-inherited property; writing border-color: inherit is a way to pull
the border color from above.
initial — returns to the property’s initial value. In the output, the cell with
font-family: initial dropped to the value serif; the inheritance of system-ui from
above was cut. This result is often not the one desired: initial goes back to the
language’s definition, not to the page’s design.
unset — behaves conditionally: if the property is an inherited one, it acts like
inherit; if not, like initial. In the output, text-align: unset dropped to
inheritance, border-color: unset to the initial value. This is the answer to the
question “what would this be if this declaration had never been written at all.”
revert — is the fourth, and answers a different question: it returns the value to what a
weaker source would have won. Writing revert in author style means going back, on
that property, to the browser default. The difference from initial shows up here: writing
color: initial on a link takes the color to the language’s initial value, while
color: revert takes it to the color the browser has defined for links.
All four can be written to every property at once with the all shorthand. all: revert
isolates an element from author style; all: unset keeps inherited properties and resets
the rest.
Value Stages
The term “computed value,” which came up in this section, needs to be defined. A declaration’s value goes through several stages from what is written to what ends up on screen:
| Stage | What happens |
|---|---|
| declared value | The text written in the source |
| cascaded value | The declaration that won the cascade |
| specified value | If there is no winner, filled in with inheritance or initial value |
| computed value | Relative units resolved, the form passed on by inheritance |
| used value | The final form once layout has been performed |
The stage carried by inheritance is the computed value. If a parent element has
font-size: 1.5em written on it, the child does not inherit the text 1.5em, it inherits
that expression’s resolved result — otherwise it would be multiplied again at every
generation, and the font size would grow exponentially.
The used-value stage, on the other hand, waits for layout. In the declaration
width: 50%, the computed value is still a percentage; the exact number only emerges once
the containing box’s width is known. This stage will be covered in detail in the box model
topic.
Using Inheritance in the Station Page
/* station.css — step 7: using inheritance as a base */ body { color: #1c2733; font-family: system-ui, sans-serif; line-height: 1.5; } /* form controls do not inherit typography by default */ input, select, textarea, button { font: inherit; color: inherit; } .measurement-table { border-collapse: collapse; } .measurement-table th, .measurement-table td { border-block-end: 1px solid #d5dbe0; }
The second rule closes an exception. Form controls do not take their typography from the
document; they carry their own default typefaces. Writing font: inherit removes this
exception and ties the controls to the document’s typography.
The third group shows that, because border color is not inherited, the border has to be written to the cells separately — the direct consequence of the behavior seen in the output’s fourth block.
Summary
- A property either inherits or, if there is no declaration, falls back to its initial value; which applies is written in the property’s definition, and text properties inherit while box-geometry properties do not.
- The initial value comes from the language’s definition; the browser’s default
appearance comes from a style file and is the weakest source in the cascade.
display: blockis not an initial value, it is a declaration written in that file. - Inheritance is the weakest source: if there is a declaration on the child element for that property, inheritance does not apply.
inheritforces the value from above,initialreturns to the language’s initial value,unsetpicks between the two based on the property’s kind,revertreturns to the value of a weaker source.- What inheritance carries is the computed value; relative units are resolved before being carried, otherwise they would be multiplied again at every generation.
Next Step
Throughout this topic, which elements rules reach and which value wins have been
resolved. The value is settled, but what that value means has not been established yet.
Where on a cell does padding: 8px add eight pixels? What is the total width of a box
given a width? The next topic defines the model every element turns into a box, and works
through that box’s sections with arithmetic.
To keep your progress and take notes, Log in
My notes
Log in to take notes.