Browser DevTools are indispensable for us front end developers. In this article, we’ll take a look at the Computed tab, a small corner of the DevTools panel that shows us big things, like how relative CSS values are resolved. We’ll also see how inheritance fits into the browser’s style computation process.
The content in the Computed tab is important because it shows us the values that the browser is actually using on the rendered website. If an element isn’t styled how you think it should be, looking at its computed values can help you understand why.
If you’re more accustomed to using the Styles tab (called Rules in Firefox), you may wonder how it differs from the Computed tab. I mean, they both show styles that apply to an element. The answer? The Computed tab displays an alphabetized list of resolved styles that include what is declared in your stylesheet, those derived from inheritance, and the browser’s defaults.
The Styles tab, on the other hand, displays the exact rulesets of a selected element exactly as they were written. So while the Styles tab might show you something like .subhead {font-size: 75%}
, the Computed tab will show you the actual font size, or what 70%
currently resolves to. For example, the actual font size for the rendered text as shown above is 13.2px
.
Next, let’s briefly review the concepts of inheritance and the cascade, two things that are a huge part of how the computed values in the Computed tab are arrived at.
Crash course on inheritance and the cascade
MDN
CSS stands for Cascading Style Sheets, and that first word cascading is incredibly important to understand – the way that the cascade behaves is key to understanding CSS.
The cascade is notable because it’s the “C” in CSS. It’s the mechanism used for resolving conflicts that exist between the different sources of style declarations for a document.
For example, imagine a stylesheet that defines the width of a div twice:
div {
width: 65vw;
}
/* Somewhere, further down */
div {
width: 85vw;
}
In this specific example, the second width wins out since it is declared last. The first width could still win with !important
but that’s technically breaking the cascade by brute force. The point here is that the cascade algorithm determines what styles apply to each element and prioritizes them in a predetermined order to settle on a value.
The cascade is applied for properties that are set explicitly, whether by the browser, the web developer, or the user. Inheritance comes into the picture when the output of the cascade is empty. When this happens, the computed value of a property on an element’s parent is pulled in as its own value for that property. For example, if you specify a color for an element, all child elements will inherit that color if you don’t specify theirs.
There are four key property values related to inheritance that we should get acquainted with before we plow ahead. We’ll be using these throughout the article.
initial
In an HTML document where the highest level of the DOM tree is the <html>
element, when we use the initial
keyword on an element like this…
…the text color for that element is black, even though the body
element is set to green
. There’s the matter of the div
selector having a higher specificity, however we’re interested in why initial
translated to black.
In plain terms, this keyword sets the default value of a property as specified in its definition table (in the CSS specs). In this case, black happens to be the browser’s implementation of the initial
color value.
I mention near the end of the article that you can learn whether or not a property is inherited by default by checking out its page on MDN. Well, you can also find the initial value for any property this way.
inherit
For non-inherited properties, this keyword forces inheritance. In the following example, the <body>
element has a solid red border. The border
property isn’t inherited by default, but we can tell our div to inherit the same red border declared on the <body>
element by using the inherit
keyword on its border
property:
unset
unset
will resolve to an inherited value if a property is inherited. Otherwise, the initial
value is used. This basically means unset
resets a property based on whether it is inherited or not. Here’s a demo that toggles unset
to show its effect on elements with different levels of specificity.
revert
If no CSS properties are set on an element, then does it get any styles at all? You bet. It uses the browser’s default styles.
For example, the initial value for the display
property for span elements is inline
, but we can specify it as block
in our stylesheet. Use the button in the following demo to toggle revert
on both the span
element’s display
and color
properties:
The span properly reverts to an inline element, but wait! Did you notice that the color of the span goes to a green color instead of the browser’s default black value? That’s because revert
allows for inheritance. It will go as far back as the browser’s default to set the color, but since we’ve explicitly set a green color on the <body>
element, that’s what is inherited.
Finding computed values in DevTools
This is where we start talking about the computed values in DevTools. Just as with the default values of properties, the computed value of a CSS property is determined by that property’s definition table in the CSS specifications. Here’s what that looks like for the height
property.
Say we use relative lengths in our CSS, like one of 10em or 70% or 5vw. Since these are “relative” to something – font-size or the viewport – they’ll need to get resolved to a pixel-absolute value. For example, an element with a 10% width may compute to 100px if the viewport is 1000px wide, but some other number altogether when the viewport width changes.
These values are calculated whenever the DOM is modified in a process called computed styles calculation. This is what lets the browser know what styles to apply to each page element.
Style calculations happen in multiple steps involving several values. These are documented in the CSS Cascading and Inheritance Level 4 specification and they all impact the final value we see in the Computed tab. Let’s take a look at those next.
Values and how they’re processed
The values defined for the style calculation process include the declared value, the specified value, the cascaded value, the computed value, the used value, and the actual value. Who knew there were so many, right?
Declared values
A declared value is any property declaration applies to an element. A browser identifies these declarations based on a few criteria, including:
- the declaration is in a stylesheet that applies to the current document
- there was a matching selector in a style declaration
- the style declaration contains valid syntax (i.e, valid property name and value)
Take the following HTML:
<main>
<p>It's not denial. I'm just selective about the reality I accept.</p>
</main>
Here are declared values that apply to the font-size
of the text:
main {
font-size: 1.2em; /* this would apply if the paragraph element wasn't targeted specifically, and even then, as an inherited value, not "declared value" */
}
main > p {
font-size: 1.5em; /* declared value */
}
Cascaded values
The list of all declared values that apply to an element are prioritized based things like these to return a single value:
- origin of the declaration (is it from the browser, developer, or another source?)
- whether or not the declaration is marked ‘!important’
- how specific a rule is (e.g,
span {}
vssection span {}
) - order of appearance (e.g, if multiple declarations apply, the last one will be used)
In other words, the cascaded value is the “winning” declaration. And if the cascade does not result in a winning declared value, well, then there is no cascaded value.
main > p {
font-size: 1.2em;
}
main > .product-description { /* the same paragraph targeted in the previous rule */
font-size: 1.2em; /* cascaded value based on both specificity and document order, ignoring all other considerations such as origin */
}
Specified values
As mentioned earlier, it is possible for the output of the cascade to be empty. However, a value still needs to be found by other means.
Now, let’s say we didn’t declare a value for a specific property on an element, but did for the parent. That’s something we often do intentionally because there’s no need to set the same value in multiple places. In this case, the inherited value for the parent is used. This is called the specified value.
In many cases, the cascaded value is also the specified value. However, it can also be an inherited value if there is no cascaded value and the property concerned is inherited, whether by default or using the inherit
keyword. If the property is not inherited, then the specified value is the property’s initial value, which, as mentioned earlier, can also be set explicitly using the initial
keyword.
In summary, the specified value is the value we intend to use on an element, with or without explicitly declaring it on that element. This is a little murky because the browser’s default can also become the specified value if nothing is declared in the stylesheet.
/* Browser default = 16px */
main > p {
/* no declared value for font-size for the paragraph element and all its ancestors */
}
Computed values
Earlier, we discussed, briefly, how relative values needed to be resolved to their pixel-absolute equivalent. This process, as already noted, is pre-determined. For example, property definition tables have a “Computed value” field that detail how specified values, in general, are resolved.
In the following example, we’re working with the em
, a relative unit. Here, the final value used when rendering the element to which the property applies is not a fixed number as seen in our declared value, but something that needs to be calculated based on a few factors.
main {
font-size: 1.2em;
}
main > p {
font-size: 1.5em; /* declared value */
}
The font-size
of the paragraph element is set to 1.5em, which is relative to the font-size
value of the main
element, 1.2em. If main is a direct child of the body element – and no additional font-size
declarations are made above that, such as by using the :root
selector – we can assume that the calculation for the paragraph’s font-size
will follow this approximate course:
Browser_Default_FontSize = 16px;
Calculated_FontSize_For_Main = 1.2 * Browser_Default_FontSize; // 19.2px
Calculated_FontSize_For_Paragraph = 1.5 * Calculated_FontSize_For_Main; // 28.8px
That 28.8px is the computed value. Here’s a demo:
Open up DevTools and check out the computed font sizes in the Computed tab.
Let’s say we’re using rem
units instead:
html {
font-size: 1.2em;
}
main {
font-size: 1.5rem;
}
div {
font-size: 1.7rem;
}
The computed value of a rem
unit is based on the font-size
of the root HTML element, so that means that the calculation changes a little bit. In this specific case, we’re using a relative unit on the HTML element as well, so the browser’s default font-size
value is used to calculate the base font-size
we’ll use to resolve all our rem
values.
Browser_Default_FontSize = 16px
Root_FontSize = 1.2 * Browser_Default_FontSize; // 19.2px
Calculated_FontSize_For_Main = 1.5 * Root_FontSize; // 28.8px
Calculated_FontSize_For_Div = 1.7 * Root_FontSize; // 32.64px
Open up DevTools again for this demo:
The value, 16px, for Browser_Default_FontSize
is commonly used by browsers, but this is subject to variation. To see your current default, select the <html>
element in DevTools and check out the font-size
that is shown for it. Note that if a value was set for the root element explicitly, just as in our example, you may have to toggle it off in the Rules tab. Next, toggle on the “Show all” or “Browser styles” (Firefox) checkbox in the Computed tab to see the default.
During inheritance, computed values are passed down to child elements from their parents. The computation process for this takes into account the four inheritance-controlling keywords we looked at earlier. In general, relative values become absolute (i.e. 1rem becomes 16px). This is also where relative URLs become absolute paths, and keywords such as bolder
(value for the font-weight
property) get resolved. You can see some more examples of this in action in the docs.
Used values
The used value is the final result after all calculations are done on the computed value. Here, all relative values are turned absolute. This used value is what will be applied (tentatively) in page layout. You might wonder why any further calculations have to happen. Wasn’t it all taken care of at the previous stage when specified values were processed to computed values?
Here’s the thing: some relative values will only be resolved to pixel-absolutes at this point. For example, a percentage-specified width might need page layout to get resolved. However, in many cases, the computed value winds up also being the used value.
Note that there are cases where a used value may not exist. According to the CSS Cascading and Inheritance Level 4 specification:
…if a property does not apply to an element, it has no used value; so, for example, the
flex
property has no used value on elements that aren’t flex items.
Actual values
Sometimes, a browser is unable to apply the used value straightaway and needs to make adjustments. This adjusted value is called the actual value. Think of instances where a font size needs to be tweaked based on available fonts, or when the browser can only use integer values during rendering and need to approximate non-integer values.
Inheritance in browser style computations
To recap, inheritance controls what value is applied to an element for a property that isn’t set explicitly. For inherited properties, this value is taken from whatever is computed on the parent element, and for non-inherited properties, the initial value for that property is set (the used value when the keyword initial
is specified).
We talked about the existence of a “computed value” earlier, but we really need to clarify something. We discussed computed values in the sense of one type of value that takes part in the style resolution process, but “computed value” is also a general term for values computed by the browser for page styling. You’ll typically understand which kind we mean by the surrounding context.
Only computed values are accessible to an inherited property. A pixel-absolute value such as 477px, a number such as 3, or a value such as left (e.g. text-align: left
) is ready for the inheritance process. A percentage value like 85% is not. When we specify relative values for properties, a final (i.e. “used”) value has to be calculated. Percentage values or other relative values will be multiplied by a reference size (font-size
, for instance) or value (e.g. the width of your device viewport). So, the final value for a property can be just what was declared or it might need further processing to be used.
You may or may not have already noticed, but the values shown in the Computed tab of the browser will not necessarily be the computed values we discussed earlier (as in computed vs. specified or used values). Rather, the values shown are the same as returned by the getComputedStyle()
function. This function returns a value which, depending on the property, will either be the computed value or the used value.
Now, let’s see some examples.
Color inheritance
main {
color: blue;
}
/* The color will inherit anyway, but we can be explicit too: */
main > p {
color: inherit;
}
The value computed for the color
property on the main element will be blue. As color
is inherited by default, we really didn’t need color: inherit
for the paragraph child element because it would wind up being blue anyway. But it helps illustrate the point.
Color values undergo their own resolution process to become used values.
Font size inheritance
main {
font-size: 1.2em;
}
main > p {
/* No styles specified */
}
As we saw earlier in the section on values and how they are processed, our relative value for font-size
will compute to an absolute value and then be inherited by the paragraph element, even if we don’t explicitly declare it (again, font-size
is inherited by default). If we had previously set styles via a global paragraph element selector, then the paragraph may gain some extra styles by virtue of the cascade. Any property values that may be inherited will be, and some properties for which the cascade and inheritance didn’t produce a value will be set to their initial value.
Percentage-specified font size inheritance
body {
font-size: 18px;
}
main {
font-size: 80%;
}
main > p {
/* No styles specified */
}
Similar to the previous example, the <main>
element’s font-size
will be absolutized in preparation for inheritance and the paragraph will inherit a font-size
that is 80% of the body’s 18px value, or 14.4px.
Forced inheritance and post-layout computation
Computed values generally resolve the specified value as much as possible without layout, but as mentioned earlier, some values can only be resolved post-layout, such as percentage-specified width
values. Although width
isn’t an inherited property, we can force inheritance for the purpose of illustrating pre-layout and post-layout style resolution.
This is a contrived example but what we’re doing is taking an element out of the page layout by setting its display
property to none
. We have two divs in our markup that inherit a width
, 50%, from their parent element <section>
. In the Computed tab in DevTools, the computed width
for the first div is absolute, having been resolved to a pixel value (243.75px for me). On the other hand, the width of the second div that was taken out of the layout using display: none
is still 50%.
We’ll imagine that the specified and computed value for the parent <section>
element is 50% (pre-layout) and the used value is as shown under the Computed tab – that’s 487.5px for me, post-layout. This value is halved for inheritance by the child divs (50% of the containing block).
These values have to be computed whenever the width of the browser’s viewport changes. So, percentage-specified values become percentage-computed values, which become pixel-used values.
Properties that inherit by default
How do you know if a property inherits by default or not? For each CSS property in the MDN docs, there is a specifications section that provides some extra details that include whether or not the property is inherited. Here’s what that looks like for the color
property:
Which properties are inherited by default and which aren’t is largely down to common sense.
MDN
Another reference option is the properties section of the W3C specs. Still another is this StackOverflow thread which may not be exhaustive at the time of writing.
Here are some examples of properties that inherit by default:
color
direction
font-family
font-size
font
letter-spacing
line-height
list-style-type
tab-size
text-align
text-justify
text-transform
visibility
word-wrap
Examples of properties that do not (but which you can force to inherit with the inherit
keyword):
box-shadow
border
content
height
margin
object-fit
opacity
padding
position
transform
transition
width
z-index
Hopefully this gives you a solid idea of how browsers compute styles and how to reference them in DevTools. As you can see, there’s a lot that goes into a value behind the scenes. Having that context goes a long way in helping you troubleshoot your work as well as furthering your general understanding of the wonderful language we know as CSS.
Further reading
- The inherit, initial, and unset values by QuirksMode.org
- CSS Inheritance: An Introduction by Asha Laxmi
- CSS Inheritance, The Cascade And Global Scope: Your New Old Worst Best Friends by Heydon Pickering
- The latest ways to deal with the cascade, inheritance and specificity by Ollie Williams
- Cascade and Inheritance by MDN
- Inheritance by MDN
- Cascade by MDN
- CSS Cascading and Inheritance Level 4 (W3C specification)
- Render-tree Construction, Layout and Paint by Ilya Grigorik
- Window.getComputedStyle() by MDN
- URL to Interactive by Aaron Gustafson
The post Computed Values: More Than Meets the Eye appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.
source https://css-tricks.com/computed-values-more-than-meets-the-eye/
No comments:
Post a Comment