The Cascade
With all the possible ways of declaring CSS styles, it frequently occurs that multiple values for a given style property could apply to a single element. You might have a fill
presentation attribute, fill
set in a matching style rule in a <style>
block, and fill
set in another style rule in an external stylesheet.
This is where the cascade comes in: the rules that govern the waterfall of different style declarations, one replacing another until a final value is determined.
For the most part, the CSS cascade works the same in SVG as it does in CSS-styled HTML. However, presentation attributes add an extra twist. This article is written from the perspective of SVG styles, but it applies to CSS in general.
The order of precedence of CSS rules is determined by the interaction of four factors:
-
The origin of the style declaration: browser defaults, web page author values, or user settings.
-
The importance of the rule (either
!important
or not). -
The specificity of the CSS selector.
-
The order in which the declarations are given; when all else is equal, later declarations replace previous ones.
The final, used value of each property on each element is the result of the CSS cascade, CSS inheritance, and additional computations based on the web page layout.
Style Declaration Origins and Importance
There are five possible origins, from the perspective of the cascade. They are ranked in the following order (for non-!important
declarations), with origins later in the list overriding origins earlier in the list:
-
Web browser (or other user agent) default styles for a particular element or pseudoclass. For example, in HTML the
<li>
element hasdisplay: list-item
, and headings usually have bold font and larger font sizes.The user-agent styles for SVG are defined in the SVG specs. The following styles are applied this way:
-
overflow
ishidden
by default on SVG elements where it has an effect (the normal default foroverflow
in CSS isvisible
); -
display
is forced to always benone
for<defs>
and other elements that are never displayed in SVG; -
new properties from the CSS Transforms specification (see Chapter 11) are adjusted for SVG elements to maintain backwards compatibility (by default) with SVG 1.1 transforms.
-
-
User preferences set in the web browser. For example, most browsers allow users to specify a default font.
-
Styles defined by the web page: in the markup, in
<style>
blocks, or from external stylesheets. These are collectively known as “author” style rules. -
Styles defined in
@keyframes
animation rules. Theanimation
property (and its longhands) cascades as a regular style property, to determine which keyframes apply on a given element. However, the effects created by the keyframes themselves are treated as a separate, high priority origin, which overrides other declarations regardless of specificity. -
Transition states. Again, the
transition
property follows normal cascade rules, but when a transition applies to another property, it applies to changes created by any other origin, including keyframe animations.
For !important
rules, the ranking of origins 1 to 4 is reversed.
Important user preferences override all author styles, including important ones. These are usually related to accessibility issues, such as setting a minimum font size or requiring high-contrast color combinations.
Tip
Because SVG text is often artistic, browsers may or may not apply user’s accessibility preferences to modify the author’s styles. They may or may not consider scaling transformations before determining if a font-size
is too small.
Important browser styles are even stronger, and usually relate to fundamental features of the language. In SVG 2, the behavior of display
on elements that are never displayed—regardless of the display
value an author might set—is defined using an !important
user-agent stylesheet rule. (In SVG 1.1, the same rule existed, it just wasn’t defined in terms of the CSS cascade.)
The CSS specs (CSS Cascade Level 3 and 4) currently say that all of these important rules should override animations, but most browsers don’t currently work that way.
Warning
In Blink, WebKit, and MS Edge, animations override important author styles. However, in Chrome and Safari (at least) they do not override important user styles like minimum font sizes. (MS Edge does not have a minimum font-size user setting.)
Selector Specificity and Source Order
When multiple style rules declared by the same origin could apply to a given element, they are ranked according to the specificity of the CSS selector used. There are three components to selector specificity, in order from least to most specific:
-
Element tag names and pseudoelement selectors.
-
Classes, pseudoclasses, and attribute-based selectors.
-
ID values.
A given selector may have many components, so the total number of components of each category is counted. If two selectors are tied for the number of id
references (possibly none), then the number of class and class-like components is counted. If still tied, the number of element components is used.
A universal selector, *
, has zero specificity when comparing stylesheet rules, but still trumps over presentation attributes, inherited values, and browser defaults.
Tip
Presentation attributes are treated as if they had selectors with a specificity slightly less than zero. Any CSS rule that applies to the same element will be more specific.
The style
attribute acts like the most specific selector of all. Technically, inline styles are the fourth component of selector specificity. However, we can’t—yet—use any other selectors (such as pseudoclasses) in a style
attribute, so there’s no need to compare any other numbers.
If two CSS declarations have the same origin, importance, and specificity, then the final tie-breaker is used: source order. The last declaration wins.
External stylesheets and <style>
blocks are ordered based on the DOM order of the elements that include them. External stylesheets included with @import
get positioned before the sheet that imported them—which is why @import
values are only allowed at the very top of a stylesheet. Within each stylesheet (or even within style
attributes), declarations are ordered in the order you typed them.
Tip
The “last declaration” doesn’t include any declarations that the CSS parser tosses out as invalid. That’s why you can create easy fallbacks for new syntax, with the new syntax declared right after the old one:
fill
:
currentColor
;
fill
:
var
(
--
primaryColor
,
navy
);
Browsers that recognize the new syntax (here, CSS variables) will use it instead of the earlier declaration. Older browsers will treat currentColor
as the last valid fill
value.
Presentation attributes don’t have any specific order, but you’re not supposed to ever have more than one attribute with the same name on the same element. If you do, the results are undefined—browsers may pick the first or last version.
Cascaded and Specified Values
Origin, specificity, and source order are used to rank all the style declarations on an element. This is the cascading process: selecting a single, top-ranked declaration for each property and element.
After the cascade, all the style declarations on each element have been reduced to a single, cascaded value for each property.
But there might not be a cascaded value—because no CSS declarations, anywhere in the cascade, mentioned that property for that element. In that case, the element is assigned either initial
or inherit
, depending on whether the style property is normally inherited. The result of this assignment is known as a specified value. Every property on every element (and pseudoelement) has a specified value.
You can also force the cascaded value to be either initial
or inherit
, by using those keywords in a CSS declaration. They are valid as a replacement for the value in any CSS property, including in SVG presentation attributes.
The initial value—which also applies to the root element, for inherited properties—is defined for each CSS property in the specifications.
Tip
The initial
keyword isn’t affected by browser defaults for particular elements. So overflow: initial
will always work out as visible
, regardless of SVG or HTML.
For example, the initial value of the stop-color
property used in gradients is black
, and it isn’t normally inherited. To change it, you need to override stop-color
on every <stop>
element.
The initial value of the stroke
property is none
, but stroke
is inherited. If you set it on a <g>
or <svg>
, it will affect all child elements, unless they have their own (cascaded) stroke
value.
The latest specs also define two other universal keywords:
-
unset
(equivalent toinitial
orinherit
, depending on if the property is normally inherited or not) -
revert
(rollback to the final value from a lower-ranked origin; in author styles, this means rollback to browser defaults)
Warning
The initial
and unset
keywords are supported in all the latest browsers, but not in older browsers such as Internet Explorer.
revert
is only supported in WebKit (as of late 2017).
Inheritance and Computed Values
Once you have a specified value (possibly inherit
or initial
) for each property on each element, you’re finished with the cascade, and ready to move on to inheritance.
Many property values are partially-computed before inheritance, creating computed values. For example, em
units are usually converted to px
. CSS variable references (var()
functions) are substituted for their values. Some calc()
functions are also simplified at this time. Colors are converted to integer rgb()
or rgba()
notation.
Warning
The original CSS Color Module Level 3 said that the currentColor
keyword should also be substituted before inheritance. This created a breaking change from how currentColor
worked in SVG. The CSS specs have since been changed, and currentColor
is now defined to inherit as a keyword. However, browsers are currently inconsistent.
The computation process works top-down in the inheritance tree, starting from the root element. The initial
and inherit
keywords are also substituted at this point: initial
with the value defined in the specs, and inherit
with the computed value from the parent element. You often need inherited values to do other parts of the computation: for example, you may need to compute an inherited font-size
in order to convert em
lengths into px
.
In SVG, the hidden cloned elements created by <use>
references inherit styles from the <use>
element itself. In other words, for style inheritance, the clones act as if they were child elements of the <use>
, although they are not child elements for CSS selector matching. HTML web components work similarly.
Warning
Firefox up to version 56 (mid-2017) incorrectly treated the cloned elements as if they were child elements of <use>
for CSS selector matching, too.
Selector specificity and rule origin have no effect on inherited values. A (low-specificity) presentation attribute will not be replaced by an inherited value, even if that inherited value was created by an !important
CSS rule or by an animation.
Tip
As we showed in Example 3-1 in the book, you can force a presentation attribute to be overridden by using the inherit
keyword in a CSS rule that applies directly to the element with the attribute.
The computed value for inheritance doesn’t include all the final computations used by the getComputedStyle()
DOM method—such as converting percentages to px
—because those calculations require the final CSS layout to be calculated.
After inheritance is complete, the browser is able to lay out the web page, converting the computed CSS values into their final used values as it goes. Percentages and auto
values are resolved, remaining calc()
functions are calculated, currentColor
is replaced (if it wasn’t previously), and various property-specific clamping and other adjustments are made.
And finally, the browser can actually paint the web page.