Using SVG with CSS3 and HTML5 — Supplementary Material

Example code and online extras for the O'Reilly Media book by Amelia Bellamy-Royds, Kurt Cagle, and Dudley Storey.

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:

  1. The origin of the style declaration: browser defaults, web page author values, or user settings.

  2. The importance of the rule (either !important or not).

  3. The specificity of the CSS selector.

  4. 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:

  1. Web browser (or other user agent) default styles for a particular element or pseudoclass. For example, in HTML the <li> element has display: 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 is hidden by default on SVG elements where it has an effect (the normal default for overflow in CSS is visible);

    • display is forced to always be none 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.

  2. User preferences set in the web browser. For example, most browsers allow users to specify a default font.

  3. Styles defined by the web page: in the markup, in <style> blocks, or from external stylesheets. These are collectively known as “author” style rules.

  4. Styles defined in @keyframes animation rules. The animation 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.

  5. 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:

  1. Element tag names and pseudoelement selectors.

  2. Classes, pseudoclasses, and attribute-based selectors.

  3. 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:

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.