What flows down the DOM tree
The second half of how CSS resolves a value: CSS inheritance, which properties flow down the DOM tree, and the rule it gives you for where each Tailwind utility belongs.
Here is a layout you’ve written a dozen times by now: a <body> that sets the font and the text color once, and trusts every element underneath to pick them up.
<body className="font-sans text-foreground"> <p>Your trial ends in 3 days.</p> <button>Upgrade now</button></body>Open this in a browser and the paragraph behaves: it renders in your sans font, in your foreground color. The button does not. It comes out in the browser’s default font, usually a slightly different sans and sometimes the system UI font, and on many browsers its text is a flat black or a faded grey, ignoring the text-foreground you set right above it. Same parent, same two utilities, two completely different outcomes.
Two questions are buried in this snippet, and they turn out to be the same question. Why do the body’s styles flow down to the paragraph on their own, and why does this one element opt out? By the end of this lesson you’ll have a precise answer to both, and something more useful besides: a rule for where every styling utility belongs on a real project, which ones you set once high up and let fall, and which ones you write on each element by hand. We’ll come back to the button at the end, because the way it escapes is the most instructive part.
Inheritance is a per-property flag
Section titled “Inheritance is a per-property flag”The mechanism behind “the paragraph picked up the font” has a name: inheritance. What most people get wrong is assuming it’s a global “styles flow downward” behavior. It isn’t. Inheritance is a flag, set property by property, baked into the CSS specification itself. For every CSS property, the spec declares one of two things: inherited: yes or inherited: no. color says yes. padding says no. That’s it, with no master switch over the top.
That flag controls what happens when an element ends up with no declared value for a property: nobody wrote a rule that touches it. (An element reaches that state after the cascade runs, the origin, layer, specificity, and source-order contest you saw in the previous lesson, How the browser picks a winning rule.) The browser can’t leave the property blank, so it fills in a value, and the flag decides which one:
- If the property is inheriting, the browser copies the computed value from the element’s parent.
- If the property is non-inheriting, the browser uses the property’s initial value , the spec-defined default for that property. The initial value is the same everywhere and has nothing to do with the parent.
One word in that first bullet carries a lot of weight: the computed value of the parent. That isn’t the value you typed on the parent. It’s the value the parent actually resolved to after its own cascade and its own unit math. The distinction looks pedantic right now, but hold onto it: it’s the entire reason one of the classic CSS bugs near the end of this lesson exists.
The payoff of inheriting properties is reach. A value you set on <html> or <body> doesn’t stop at the first child. It falls all the way down, element to element, to every descendant, unless some rule overrides it on the way. That’s why font-sans on <body> is genuinely enough for your whole app. You aren’t relying on a wrapper component to forward it: you set it at the top, and inheritance carries it to the deepest nested <span> you’ll ever write, for free.
It flows down the DOM tree, not the JSX tree
Section titled “It flows down the DOM tree, not the JSX tree”One caveat trips up almost everyone coming to React, because they picture the component outline in their head. Inheritance walks the rendered DOM tree, from a real parent element to a real child element in the page the browser builds. It has nothing to do with how your JSX components are nested.
Those two trees are not the same shape. A <Card> component might sit ten layers deep in your JSX, wrapping <CardHeader> wrapping <CardTitle>, but in the DOM it contributes only whatever element it renders at its root, maybe a single <div>. Inheritance sees that <div>, not the component. So you don’t pass a font down through props, and wrapping a subtree in a styling component does nothing on its own. You set an inheriting property on an ancestor DOM node, and it reaches the descendant DOM nodes below it. When you reach for inheritance, picture the rendered page, not your component file.
What inherits, and what doesn’t
Section titled “What inherits, and what doesn’t”This is the part worth learning, and you don’t learn it as two lists to memorize. You learn one heuristic, and the lists fall out of it:
If a property describes the text, it inherits. If it describes the box (its size, position, background, or visual effects), it doesn’t.
That split is deliberate, not arbitrary, which is what makes it stick. Think about what each kind of style is for. Text styling is almost always something you want to apply to a whole region at once: pick a font, a color, and a line height for an article, and you want every paragraph, list, and inline <em> inside it to follow without you repeating yourself. Inheritance is exactly that “apply to everything underneath” behavior. Box geometry is the opposite. No two elements share a width by coincidence, and a child inheriting its parent’s padding would be a problem rather than a convenience, because every nested box would balloon. So the spec made the text-ish properties inherit and the box-ish ones not, because that is what’s useful. Once you see it as a choice rather than trivia, you can predict the answer for a property you’ve never met.
Here are the two families filled in. You don’t need to commit them to memory: read them as confirmation that the heuristic holds.
Two entries on that list are worth singling out.
The first is cursor and visibility: both inherit, even though neither feels like text. cursor inherits so that setting a pointer on a clickable region covers the text and icons inside it without you re-applying it to each child. visibility inherits so that hiding a container hides its contents. They’re the heuristic’s edge cases, so keep them in mind; they show up in the exercise.
The second entry quietly powers your whole theme: CSS custom properties inherit. When you write --color-foreground and a .dark block on <html> overrides it, that override reaches every component in the app. There’s no framework magic behind this. Custom properties are inheriting properties like any other, and <html> is the ancestor of everything. That’s the substrate your design tokens are built on, and it gets the full treatment later in this chapter, in Custom properties and the three-tier token model. For now, file the one fact: text styles flow from <body>, and tokens flow from :root.
Now connect both halves back to Tailwind, because this is where the lesson stops being theory. Tailwind is an atomic-utility system: one class, one declaration, on one element. Each family makes a different demand of that model. The non-inheriting half is per-element anyway, so writing px-4 on each element that needs padding is exactly right, and Tailwind never tries to make box utilities cascade, because box properties don’t. The inheriting half is what lets the utility list on any given element stay short: you set font-sans text-foreground once on <body>, inheritance carries it everywhere, and you never repeat it. That’s why your leaf elements aren’t drowning in repeated typography classes. So knowing which family a property is in is the same as knowing whether to hoist its utility to an ancestor or keep it local, an architecture decision you’ll make on every component.
Predict the family
Section titled “Predict the family”This round tests the heuristic, not your memory. Some of these are easy, and at least one is a trap that feels like it belongs in the other bucket. Decide each one by asking “does this describe the text, or the box?” rather than by trying to recall a list.
Predict each property's family using the text-vs-box heuristic — does it describe the text, or the box? Drag each item into the bucket it belongs to, then press Check.
colorline-heightcursorvisibilitytext-shadowpaddingwidthbox-shadowbackground-colorTwo of these catch people. text-shadow is a visual effect despite its name, so it does not inherit: the word “text” is a red herring, and what matters is that it paints a box-level shadow. And cursor and visibility inherit despite feeling box-ish, the heuristic’s two genuine exceptions. Hold those three, and you’ve got the whole classification.
Try it: change the parent, watch the children
Section titled “Try it: change the parent, watch the children”Lists and heuristics get you most of the way, but inheritance is a behavior, and the fastest way to internalize a behavior is to drive it yourself. The playground below is a small DOM subtree: a parent box wrapping a heading, a paragraph, and a nested label. The controls all change the parent. Watch what the children do.
Drag the font size up and every text node inside grows together (heading, paragraph, and label) because font-size inherits and they’re all reading the parent’s value. Change the color and the same thing happens: all of them shift at once. Switch the font family and the whole subtree re-renders in the new face. That’s the inheriting half, live: one change at the top, and the entire subtree follows.
Then drag the padding slider. Only the parent box moves, its walls pushing outward, while the children sit exactly where they were. No child inherits the padding: the property stops at the element that declared it. Font-size flowing through while padding stays put, in the very same widget, is the whole lesson in one gesture.
Account settings
Manage your plan and billing details.
ProTelling a property to inherit on purpose: the keywords
Section titled “Telling a property to inherit on purpose: the keywords”So far inheritance has been the fallback, what happens when no rule applies. CSS also lets you ask for it explicitly. There are a handful of global keywords you can set on any property to override whatever the cascade decided and force a specific resolution instead. You’ll almost never write these by hand on a 2026 Tailwind project, but you’ll read them in other people’s CSS and see them in DevTools, so they’re worth recognizing.
.a { color: inherit; }.b { margin: initial; }.c { font-size: unset; }.d { display: revert; }The pair to keep straight is initial versus revert. initial wipes the property to the spec’s default, which is frequently not what you see on screen. For example, display: initial is inline, even for a <div>, because the browser’s own stylesheet is what made the <div> a block in the first place. revert rolls back to that browser stylesheet instead, returning the property to what the browser would have rendered. There’s also revert-layer, which rolls back just to the previous cascade layer, a direct tie to the layer model from the last lesson, though you’ll reach for it even more rarely.
On a real project you don’t write these raw. You write the utility form, and Tailwind emits the declaration for you:
<span className="text-inherit">…</span> {/* → color: inherit */}<div className="bg-transparent border-transparent">…</div>text-inherit, bg-transparent, border-transparent, and the occasional arbitrary [all:revert] cover essentially every real case. So when you spot a raw inherit or initial in a hand-written rule, read it as a small warning sign, usually that someone is fighting a cascade they should have organized with layers instead. As in the last lesson, the better instinct is to reach for structure rather than a keyword that papers over the conflict.
Why form controls don’t listen
Section titled “Why form controls don’t listen”Now we can solve the opening mystery. Why did the paragraph pick up the body’s font and color while the <button> shrugged them off?
The mechanism isn’t an exception to inheritance. It’s inheritance working exactly as specified. The browser ships its own stylesheet, the user-agent stylesheet , and that stylesheet contains rules that set font-family and color directly on form controls: <button>, <input>, <textarea>, and <select>. Once there’s a value declared directly on the element itself, the cascade has a winner, and it never falls through to inheritance. Recall from the last lesson that an inherited value is the weakest possible source, literally the absence of a declaration, so any real declaration beats it. The body’s font-family reaches the button, finds that the user-agent stylesheet already declared one on the button, and loses. The button isn’t ignoring inheritance. It simply isn’t relying on it, because something already set the value it needs.
Why do browsers do this at all? Historically, form controls were rendered by the operating system as native widgets, a real OS button or a real OS dropdown, and the user-agent stylesheet pins their typography so they keep looking like controls instead of inheriting whatever a page throws at them. It’s a deliberate platform decision, not an accident, which is why the fix is deliberate too.
/* Browser's user-agent stylesheet — you don't write this */button, input, select, textarea { font-family: system-ui;}The control already carries a declared font-family, so the body’s font never reaches it: an inherited value is the weakest source in the cascade and loses to any real declaration.
/* Tailwind Preflight (ships with @import "tailwindcss") */button, input, select, textarea { font: inherit; color: inherit;}Preflight ships exactly one rule that opts the controls back into inheritance. With font: inherit; color: inherit the body font reaches them again. The full Preflight story is the next lesson.
That second tab is the whole resolution. Tailwind’s Preflight, the reset that loads with @import "tailwindcss", ships exactly one rule for this: button, input, select, textarea { font: inherit; color: inherit; … }. All it does is turn inheritance back on for the controls by setting their font and color to inherit, so your <body> font reaches your buttons after all. Preflight does a great deal more than this, and the next lesson, Preflight, the deliberately blank canvas, gives it the full treatment. The point to take from here is just that the fix is to re-enable inheritance.
So on a real Tailwind project you don’t hand-set fonts on form controls, and you don’t fight Preflight, because it already inherited them for you. The shadcn <Button> and <Input> components you’ll meet later wrap these controls with their own styling on top, so in practice you rarely touch a bare <button> at all. The one time you’d reach for a per-element utility is a stubborn browser-specific holdout that Preflight didn’t catch.
Without Preflight, a bare <button> inside <body className="text-red-500"> still renders with the browser’s default text color, not red. Open DevTools, select the button, and look at its Computed color: it traces to a rule on button in the user-agent stylesheet — and next to it, dimmed and struck through, sits an Inherited from body entry carrying red. What does that picture tell you?
text-red-500 utility silently failed to apply — that struck-through Inherited from body entry means the class never reached the <body>.!important; without that, an inherited color can’t override a plain one.color declared straight on it by the browser’s stylesheet, so the inherited red — the weakest source there is — gets struck through and loses.color simply doesn’t reach descendants on its own, so the body’s red was never a candidate for the button in the first place.Inherited from body line is the giveaway: the red genuinely did flow down to the button — color is an inheriting property — but an inherited value is the weakest thing in the cascade, the mere absence of a declaration. The user-agent stylesheet already declared a color right on the button, and any real declaration outranks an inherited one, so the inherited red is crossed out. Nothing failed and no !important is needed; Preflight’s one color: inherit rule simply puts the control back on inheritance so the body’s color reaches it.currentColor: the one inheritance trick you’ll write by hand
Section titled “currentColor: the one inheritance trick you’ll write by hand”There’s exactly one inheritance-powered pattern you’ll author directly, and you’ll reach for it constantly once you have icons on the page, so it gets its own section.
currentColor is a CSS keyword that resolves to the computed value of color on the element where you use it. That’s the whole idea: wherever you write currentColor, it means “whatever the text color is right here.” And because color inherits, the text color right here is usually one that flowed down from an ancestor. So currentColor lets any property piggyback on the inherited text color and follow it around the page on its own.
The canonical use is SVG icons. An inline icon with fill="currentColor" paints itself in the text color of wherever you drop it. Put a check icon next to a line of body text and it matches the text with no effort. Set text-blue-500 on a button, and because both the label and the icon read currentColor, both turn blue from that one utility: you colored the parent once and the icon came along. This is exactly how Lucide and shadcn icons behave by default, which is why icon color just works in that ecosystem without you ever styling the icon directly.
The Tailwind form is fill-current (→ fill: currentColor) and stroke-current (→ stroke: currentColor). In practice you’ll often write neither, because the icon already defaults to currentColor and you just set text-* on the parent. The exercise below makes the connection concrete: make the icon follow the label’s color.
The button colors its label with `text-indigo-600`, but the check icon is pinned to a hardcoded red — it ignores the label. Make the icon follow the text color so it turns indigo too, by pointing the SVG's `fill` at the current color (or using the `fill-current` utility). Match the target.
One more place currentColor hides is borders. Preflight resets borders to border: 0 solid currentColor, which is why a fresh border utility tints to your text color until you set an explicit border-* color. The next lesson picks up that thread. For now, know that a border the same color as the text is the default you’re starting from, and it’s currentColor doing the work.
When the page lies: inheritance look-alikes
Section titled “When the page lies: inheritance look-alikes”A few effects look like inheritance, with a parent changing and the children visibly responding, but the mechanism underneath is something else. Getting these wrong leads to confusing bugs, because the fix you reach for depends on what’s actually happening. Here are the four to watch for.
opacity is not inherited, even though it looks like it. Set opacity: 0.5 on a parent and every child fades, so people conclude opacity inherits. It doesn’t. What happens is that the parent and its whole subtree get flattened into a single composited group, and that group is made half-transparent as one unit. The children fade as a side effect of the group being see-through, not because each child got an opacity value. The tell that proves it: a child cannot set opacity: 1 to become solid again. The group is already half-transparent and the child is painted inside it, so there’s nothing for the child to opt out of. Contrast that with a genuinely inherited property like color, which a child can freely override. That difference is the whole distinction, and it’s worth seeing side by side.
visibility: hidden is inherited, and a child can reverse it. Hide a parent with visibility: hidden and its children vanish too, because visibility inherits. But unlike most inherited values, a child can explicitly set visibility: visible and reappear, while the element still takes up its layout space. That reversibility is what makes visibility different from display: none, which does not inherit and removes the element from layout entirely. There’s no bringing back a single child of a display: none ancestor, because the whole subtree is gone from the render. (When to actually use which for hiding things is a decision the box-model chapter handles; here, only the inheritance facts matter.)
Transparent backgrounds are passthrough, not inheritance. A child with no background of its own shows the parent’s background, which looks inherited. It isn’t: background-color doesn’t inherit. The default background-color is transparent, so the child is simply see-through, and you’re looking at the parent’s background through it. The tell: change the parent’s background and what shows changes, but inspect the child and its background-color is still transparent. It never took the parent’s color, it just isn’t covering it.
em font-size compounds, so prefer rem. Recall the word that carried weight earlier: a child inherits the parent’s computed font-size. Now picture nesting elements that each set font-size: 1.25em. The first is 1.25× its parent. The second is 1.25× that, already 1.56×. The third is 1.95×. Each level multiplies against the computed result of the level above, and the text runs away on you. This is the bug that the “computed value of the parent” detail was setting up. The fix, and the course default, is rem: it’s relative to the root font-size, not the parent, so it never compounds. In practice Tailwind’s text-* scale emits rem for you, so you get the non-compounding behavior just by using the scale. The typography chapter goes deeper; the habit to form now is rem over em.
Four claims, four mechanisms. Run the round below to lock in which is which: each statement targets one of the tells above.
Each claim is about a case where the page's behavior either is, or only looks like, inheritance. Mark each statement True or False.
opacity is an inheriting property — that’s why setting it on a parent fades the children.
opacity doesn’t inherit. The parent and its subtree flatten into a single composited group that’s made translucent as a whole; the children fade as a side effect of the group being see-through. The tell: a child can’t set opacity: 1 to become solid again, because it’s painted inside an already-transparent group.A child can override an inherited color, and a child can set visibility: visible to reappear inside a visibility: hidden parent.
color and visibility inherit, and an inherited value is the weakest source in the cascade — the mere absence of a declaration. So a child’s own declaration always outranks it, which is exactly why a child can re-show itself with visibility: visible.A child element with no background-color shows its parent’s background because its own background defaults to transparent, not because background inherits.
background-color doesn’t inherit; its default is transparent, so you’re seeing the parent’s background through the child. The tell: inspect the child and its own background-color is still transparent — it never took the parent’s color, it just isn’t covering it.Nesting elements that each set font-size: 1.25rem makes the text compound and grow at every level.
em, not rem. em is relative to the parent’s computed font-size, so it multiplies at every level and runs away. rem is relative to the root font-size and never compounds — which is why Tailwind’s text-* scale emits rem.Reveal card-by-card review
The placement rule: typography up, box-model down
Section titled “The placement rule: typography up, box-model down”Everything in this lesson converges on a single rule, and it’s the thing you’ll carry to every project you build:
Put typography and color utilities on
<body>(or a high ancestor). They inherit, so they reach the whole app for free. Put box-model and layout utilities on each element. They don’t inherit, so they have to be local.
Tokens follow the same logic one level up: your custom-property tokens go on :root and .dark, because custom properties inherit, which makes them the theming substrate the whole tree reads from. That’s a topic for later in the chapter.
Look at what that produces in a real codebase. Your root layout sets the typographic baseline once, and your leaf components carry only the box and layout utilities they each individually need, with no typography repeated, because it already flowed down.
<body className="font-sans text-foreground antialiased"> {children}</body>
// components/plan-badge.tsx<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-muted"> <CheckIcon className="size-4" /> Pro plan</span>Typography and color live once, high up, on <body>. Both inherit, so every element the layout renders below picks up this font and this color for free, and you never repeat them.
<body className="font-sans text-foreground antialiased"> {children}</body>
// components/plan-badge.tsx<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-muted"> <CheckIcon className="size-4" /> Pro plan</span>Box-model and layout utilities live on the element itself. None of these inherit, so the badge, like every other leaf, declares the spacing, shape, and background it individually needs, right where it’s used.
<body className="font-sans text-foreground antialiased"> {children}</body>
// components/plan-badge.tsx<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-muted"> <CheckIcon className="size-4" /> Pro plan</span>Now read the leaf for what isn’t there: no font-*, no text-*. The badge’s typography flowed down from <body> and never needed restating. That absence is inheritance keeping the utility list short, which is the point of the rule.
That’s the reframe to close on. Inheritance isn’t a quirk you memorize for a quiz; it’s the lever that decides where a style lives. Knowing what flows down keeps your utility lists short and your styling decisions in the right place: one element’s job gets one element’s utilities, and the whole app’s typography gets one ancestor’s.
Reading inheritance in DevTools
Section titled “Reading inheritance in DevTools”This is the same instrument as the last lesson, pointed at a new target. When a style isn’t behaving and you suspect inheritance, the browser will show you exactly where each value came from.
The Styles panel lists an element’s own rules first, then, dimmed below them, sections headed Inherited from <selector>, one per ancestor that contributed an inherited value. So you can see at a glance that the color on this element actually came from a rule on body, three ancestors up. The Computed panel goes one better: expand any property and it traces where the final value resolved from, with inherited properties flagged by the ancestor they came from.
color came from body.
padding
doesn't inherit, so it resolved on the element, with no ancestor line.
The move is the same three clicks as before: open Computed, find the property that’s misbehaving, and read where it resolved from. If it’s an inheriting property and it’s not taking effect, the panel tells you whether it inherited the value you expected or got overridden somewhere on the way down. The cascade trace from the last lesson and this inheritance trace are the same skill, just aimed at a different question.
External resources
Section titled “External resources”Durable references if you want the spec-level detail behind the families and the keywords.
Google's Learn CSS module on inheritance and the control keywords, with a live playground and a self-check quiz.
The Learn CSS walkthrough of inheritance and the global keywords, with live examples.
The keyword that resolves to the element's computed color — the icon-tinting trick in reference form.