inheritance – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Mon, 23 May 2022 17:40:35 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 inheritance – CSS-Tricks https://css-tricks.com 32 32 45537868 Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library https://css-tricks.com/defining-and-applying-ui-themes-using-the-mimcss-css-in-js-library/ https://css-tricks.com/defining-and-applying-ui-themes-using-the-mimcss-css-in-js-library/#comments Mon, 15 Nov 2021 15:32:13 +0000 https://css-tricks.com/?p=355894 Theming UI refers to the ability to perform a change in visual styles in a consistent manner that defines the “look and feel” of a site. Swapping color palettes, à la dark mode or some other means, is a good …


Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Theming UI refers to the ability to perform a change in visual styles in a consistent manner that defines the “look and feel” of a site. Swapping color palettes, à la dark mode or some other means, is a good example. From the user’s perspective, theming involves changing visual styles, whether it’s with UI for selecting a theme style, or the site automatically respecting the user’s color theme preference at the OS-level. From the developer’s perspective, tools used for theming should be easy-to-use and define themes at develop-time, before applying them at runtime.

This article describes how to approach theming with Mimcss, a CSS-in-JS library, using class inheritance—a method that should be intuitive for most developer as theming is usually about overriding CSS property values, and inheritance is perfect for those overrides.

Full discloser: I am the author of Mimcss. If you consider this a shameless promotion, you are not far from the truth. Nevertheless, I really do believe that the theming technique we’re covering in this article is unique, intuitive and worth exploring.

General theming considerations

Styling in web UI is implemented by having HTML elements reference CSS entities (classes, IDs, etc.). Since both HTML and CSS are dynamic in nature, changing visual representation can be achieved by one of the following methods:

  1. Changing the CSS selector of an HTML element, such as a different class name or ID.
  2. Changing actual CSS styling for that HTML element while preserving the selector.

Depending on the context, one method can be more efficient than another. Themes are usually defined by a limited number of style entities. Yes, themes are more than just a collection of colors and fonts—they can define paddings, margins, layouts, animations and so on . However, it seems that the number of CSS entities defined by a theme might be less than a number of HTML elements referencing these entities, especially if we are talking about heavy widgets such as tables, trees or code editors. With this assumption, when we want to change a theme, we’d rather replace style definitions than go over the HTML elements and (most likely) change the values of their class attributes.

Theming in plain CSS

In regular CSS, one way theming is supported is by using alternate stylesheets. This allows developers to link up multiple CSS files in the HTML <head>:

<link href="default.css" rel="stylesheet" type="text/css" title="Default Style">
<link href="fancy.css" rel="alternate stylesheet" type="text/css" title="Fancy">
<link href="basic.css" rel="alternate stylesheet" type="text/css" title="Basic">

Only one of the above stylesheets can be active at any given time and browsers are expected to provide the UI through which the user chooses a theme name taken from the values of the <link> element’s title attribute. The CSS rule names (i.e. class names) within the alternative stylesheets are expected to be identical, like:

/* default.css */
.element {
  color: #fff;
}

/* basic.css */
.element {
  color: #333;
}

This way, when the browser activates a different stylesheet, no HTML changes are required. The browser just recalculates styles (and layout) and repaints the page based on the “winning” values, as determined by The Cascade.

Alternate stylesheets, unfortunately, are not well-supported by mainstream browsers and, in some of them, work only with special extensions. As we will see later, Mimcss builds upon the idea of alternate stylesheets, but leverages it in a pure TypeScript framework.

Theming in CSS-in-JS

There are too many CSS-in-JS libraries out there, and there is no way we can completely cover how theming works in CSS-in-JS in a single post to do it justice. As far as CSS-in-JS libraries that are tightly integrated with React (e.g. Styled Components), theming is implemented on the ThemeProvider component and the Context API, or on the withTheme higher-order component. In both cases, changing a theme leads to re-rendering. As far as CSS-in-JS libraries that are framework-agnostic, theming is achieved via proprietary mechanisms, if theming is even supported at all.

The majority of the CSS-in-JS libraries—both React-specific and framework-agnostic—are focused on “scoping” style rules to components and thus are mostly concerned with creating unique names for CSS entities (e.g. CSS classes). In such environments, changing a theme necessarily means changing the HTML. This goes against the alternative stylesheets approach described above, in which theming is achieved by just changing the styles.

Here is where Mimcss library is different. It tries to combine the best of both theming worlds. On one hand, Mimcss follows the alternate stylesheets approach by defining multiple variants of stylesheets with identically named CSS entities. On the other hand, it offers the object-oriented approach and powerful TypeScript typing system with all the advantages of CSS-in-JS dynamic programming and type safety.

Theming in Mimcss

Mimcss is in that latter group of CSS-in-JS libraries in that it’s framework-agnostic. But it’s also created with the primary objective of allowing everything that native CSS allows in a type-safe manner, while leveraging the full power of the TypeScript’s typing system. In particular, Mimcss uses TypeScript classes to mimic the native CSS stylesheet files. Just as CSS files contain rules, the Mimcss Style Definition classes contain rules.

Classes open up the opportunity to use class inheritance to implement theming. The general idea is that a base class declares CSS rules used by the themes while derived classes provide different style property values for these rules. This is very similar to the native alternative stylesheets approach: activate a different theme class and, without any changes to the HTML code, the styles change.

But first, let’s very briefly touch on how styles are defined in Mimcss.

Mimcss basics

Stylesheets in Mimcss are modeled as Style Definition classes, which define CSS rules as their properties. For example:

import * as css from "mimcss"

class MyStyles extends css.StyleDefinition
{
  significant = this.$class({
    color: "orange",
    fontStyle: "italic"
  })

  critical = this.$id({
    color: "red",
    fontWeight: 700
  })
}

The Mimcss syntax tries to be as close to regular CSS as possible. It is slightly more verbose, of course; after all, it is pure TypeScript that doesn’t require any plug-ins or pre-processing. But it still follows regular CSS patterns: for every rule, there is the rule name (e.g. significant), what type of rule it is (e.g. $class), and the style properties the rule contains.

In addition to CSS classes and IDs, style definition properties can define other CSS rules, e.g. tags, keyframes, custom CSS properties, style rules with arbitrary selectors, media, @font-face, counters, and so on. Mimcss also supports nested rules including those with pseudo classes and pseudo-elements.

After a style definition class is defined, the styles should be activated:

let styles = css.activate(MyStyles);

Activating styles creates an instance of the style definition class and writes the CSS rules to the DOM. In order to use the styles, we reference the instance’s properties in our HTML rendering code:

render()
{
  return <div>
    <p className={styles.significant.name}>
      This is a significant paragraph.
    </p>
    <p id={styles.critical.name}>
      This is a critical paragraph.
    </p>
  </div>
}

We use styles.significant.name as a CSS class name. Note that the styles.significant property is not a string, but an object that has the name property and the CSS class name. The property itself also provides access to the CSS Object Model rule, which allows direct rule manipulation; this, however, is outside of the scope of this article (although Louis Lazaris has a great article on it).

If the styles are no longer needed, they can be deactivated which removes them from the DOM:

css.deactivate(styles);

The CSS class and ID names are uniquely generated by Mimcss. The generation mechanism is different in development and production versions of the library. For example, for the significant CSS class, the name is generated as MyStyles_significant in the development version, and as something like n2 in the production version. The names are generated when the style definition class is activated for the first time and they remain the same no matter how many times the class is activated and deactivated. How the names are generated depends on in what class they were first declared and this becomes very important when we start inheriting style definitions.

Style definition inheritance

Let’s look at a simple example and see what Mimcss does in the presence of inheritance:

class Base extends css.StyleDefinition
{
  pad4 = this.$class({ padding: 4 })
}
class Derived extends Base
{
  pad8 = this.$class({ padding: 8 })
}
let derived = css.activate(Derived);

Nothing surprising happens when we activate the Derived class: the derived variable provides access to both the pad4 and the pad8 CSS classes. Mimcss generates a unique CSS class name for each of these properties. The names of the classes are Base_pad4 and Derived_pad8 in the development version of the library.

Interesting things start happening when the Derived class overrides a property from the base class:

class Base extends css.StyleDefinition
{
  pad = this.$class({ padding: 4 })
}
class Derived extends Base
{
  pad = this.$class({ padding: 8 })
}
let derived = css.activate(Derived);

There is a single name generated for the derived.pad.name variable. The name is Base_pad; however, the style is { padding: 8px }. That is, the name is generated using the name of the base class, while the style is taken from the derived class.

Let’s try another style definition class that derives from the same Base class:

class AnotherDerived extends Base
{
  pad = this.$class({ padding: 16 })
}
let anotherDerived = css.activate(AnotherDerived);

As expected, the anotherDerived.pad.name has the value of Base_pad and the style is { padding: 16px }. Thus, no matter how many different derived classes we may have, they all use the same name for the inherited properties, but different styles are assigned to them. This is the key Mimcss feature that allows us to use style definition inheritance for theming.

Creating themes in Mimcss

The main idea of theming in Mimcss is to have a theme declaration class that declares several CSS rules, and to have multiple implementation classes that are derived from the declaration while overriding these rules by providing actual styles values. When we need CSS class names, as well as other named CSS entities in our code, we can use the properties from the theme declaration class. Then we can activate either this or that implementation class and, voilà, we can completely change the styling of our application with very little code.

Let’s consider a very simple example that nicely demonstrates the overall approach to theming in Mimcss.: a theme simply defines the shape and style of an element’s border.

First, we need to create the theme declaration class. Theme declarations are classes that derive from the ThemeDefinition class, which itself derives from the StyleDefinition class (there is an explanation why we need the ThemeDefinition class and why themes should not derive directly from the StyleDefinition class, but this is a topic for another day).

class BorderTheme extends css.ThemeDefinition
{
  borderShape = this.$class()
}

The BorderTheme class defines a single CSS class, borderShape. Note that we haven’t specified any styles for it. We are using this class only to define the borderShape property type, and let Mimcss create a unique name for it. In a sense, it is a lot like a method declaration in an interface—it declares its signature, which should be implemented by the derived classes.

Now let’s define two actual themes—using SquareBorderTheme and RoundBorderTheme classes—that derive from the BorderTheme class and override the borderShape property by specifying different style parameters.

class SquareBorderTheme extends BorderTheme
{
  borderShape = this.$class({
    border: ["thin", "solid", "green"],
    borderInlineStartWidth: "thick"
  })
}

class RoundBorderTheme extends BorderTheme
{
  borderShape = this.$class({
    border: ["medium", "solid", "blue"],
    borderRadius: 8 // Mimcss will convert 8 to 8px
  })
}

TypeScript ensures that the derived classes can only override a property using the same type that was declared in the base class which, in our case, is an internal Mimcss type used for defining CSS classes. That means that developers cannot use the borderShape property to mistakenly declare a different CSS rule because it leads to a compilation error.

We can now activate one of the themes as the default theme:

let theme: BorderTheme = css.activate(SquareBorderTheme);

When Mimcss first activates a style definition class, it generates unique names for all of CSS entities defined in the class. As we have seen before, the name generated for the borderShape property is generated once and will be reused when other classes deriving from the BorderTheme class are activated.

The activate function returns an instance of the activated class, which we store in the theme variable of type BorderTheme. Having this variable tells the TypeScript compiler that it has access to all the properties from the BorderTheme. This allows us to write the following rendering code for a fictional component:

render()
{
  return <div>
    <input type="text" className={theme.borderShape.name} />
  </div>
}

All that is left to write is the code that allows the user to choose one of the two themes and activate it.

onToggleTheme()
{
  if (theme instanceof SquareBorderTheme)
    theme = css.activate(RoundBorderTheme);
  else
    theme = css.activate(SquareBorderTheme);
}

Note that we didn’t have to deactivate the old theme. One of the features of the ThemeDefinition class (as opposed to the StyleDefintion class) is that for every theme declaration class, it allows only a single theme to be active at the same time. That is, in our case, either RoundBorderTheme or SquareBorderTheme can be active, but never both. Of course, for multiple theme hierarchies, multiple themes can be simultaneously active. That is, if we have another hierarchy with the ColorTheme declaration class and the derived DarkTheme and LightTheme classes, a single ColorTheme-derived class can be co-active with a single BorderTheme-derived class. However, DarkTheme and LightTheme cannot be active at the same time.

Referencing Mimcss themes

In the example we just looked at, we used a theme object directly but themes frequently define elements like colors, sizes, and fonts that can be referenced by other style definitions. This is especially useful for separating the code that defines themes from the code that defines styles for a component that only wants to use the elements defined by the currently active theme.

CSS custom properties are perfect for declaring elements from which styles can be built. So, let’s define two custom properties in our themes: one for the foreground color, and one for the background color. We can also create a simple component and define a separate style definition class for it. Here is how we define the theme declaration class:

class ColorTheme extends css.ThemeDefinition
{
  bgColor = this.$var( "color")
  frColor = this.$var( "color")
}

The $var method defines a CSS custom property. The first parameter specifies the name of the CSS style property, which determines acceptable property values. Note that we don’t specify the actual values here; in the declaration class, we only want Mimcss to create unique names for the custom CSS properties (e.g. --n13) while the values are specified in the theme implementation classes, which we do next.

class LightTheme extends ColorTheme
{
  bgColor = this.$var( "color", "white")
  frColor = this.$var( "color", "black")
}

class DarkTheme extendsBorderTheme
{
  bgColor = this.$var( "color", "black")
  frColor = this.$var( "color", "white")
}

Thanks to the Mimcss (and of course TypeScript’s) typing system, developers cannot mistakenly reuse, say, the bgColor property with a different type; nor they can specify values that are not acceptable for a color type. Doing so would immediately produce a compilation error, which may save developers quite a few cycles (one of the declared goals of Mimcss).

Let’s define styles for our component by referencing the theme’s custom CSS properties:

class MyStyles extends css.StyleDefinition
{
  theme = this.$use(ColorTheme)

  container = this.$class({
    color: this.theme.fgColor,
    backgroundColor: this.theme.bgColor,
  })
}

The MyStyles style definition class references the ColorTheme class by calling the Mimcss $use method. This returns an instance of the ColorTheme class through which all its properties can be accessed and used to assign values to CSS properties.

We don’t need to write the var() function invocation because it’s already done by Mimcss when the $var property is referenced. In effect, the CSS class for the container property creates the following CSS rule (with uniquely generated names, of course):

.container {
  color: var(--fgColor);
  backgroundColor: var(--bgColor);
}

Now we can define our component (in pseudo-React style):

class MyComponent extends Component
{
  private styles = css.activate(MyStyles);

  componentWillUnmount()
  {
    css.deactivate(this.styles);
  }

  render()
  {
    return <div className={this.styles.container.name}>
      This area will change colors depending on a selected theme.
    </div>
  }
}

Note one important thing in the above code: our component is completely decoupled from the classes that implement actual themes. The only class our component needs to know about is the theme declaration class ColorTheme. This opens a door to easily “externalize” creation of themes—they can be created by third-party vendors and delivered as regular JavaScript packages. As long as they derive from the ColorTheme class, they can be activated and our component reflects their values.

Imagine creating a theme declaration class for, say, Material Design styles along with multiple theme classes that derive from this class. The only caveat is that since we are using an existing system, the actual names of the CSS properties cannot be generated by Mimcss—they must be the exact names that the Material Design system uses (e.g. --mdc-theme--primary). Thankfully, for all named CSS entities, Mimcss provides a way to override its internal name generation mechanism and use an explicitly provided name. Here is how it can be done with Material Design CSS properties:

class MaterialDesignThemeBase extends css.ThemeDefinition
{
  primary = this.$var( "color", undefined, "mdc-theme--primary")
  onPrimary = this.$var( "color", undefined, "mdc-theme--on-primary")
  // ...
}

The third parameter in the $var call is the name, which is given to the CSS custom property. The second parameter is set to undefined meaning we aren’t providing any value for the property since this is a theme declaration, and not a concrete theme implementation.

The implementation classes do not need to worry about specifying the correct names because all name assignments are based on the theme declaration class:

class MyMaterialDesignTheme extends MaterialDesignThemeBase
{
  primary = this.$var( "color", "lightslategray")
  onPrimary = this.$var( "color", "navy")
  // ...
}

Multiple themes on one page

As mentioned earlier, only a single theme implementation can be active from among the themes derived from the same theme declaration class. The reason is that different theme implementations define different values for the CSS rules with the same names. Thus, if multiple theme implementations were allowed to be active at the same time, we would have multiple definitions of identically-named CSS rules. This is, of course, a recipe for disaster.

Normally, having a single theme active at a time is not a problem at all—it is likely what we want in most cases. Themes usually define the overall look and feel of the entire page and there is no need to have different page sections to use different themes. What if, however, we are in that rare situation where we do need to apply different themes to different parts of our page? For example, what if before a user chooses a light or dark theme, we want to allow them to compare the two modes side-by-side?

The solution is based on the fact that custom CSS properties can be redefined under CSS rules. Since theme definition classes usually contain a lot of custom CSS properties, Mimcss provides an easy way to use their values from different themes under different CSS rules.

Let’s consider an example where we need to display two elements using two different themes on the same page. The idea is to create a style definition class for our component so that we could write the following rendering code:

public render()
{
  return <div>
    <div className={this.styles.top.name}>
      This should be black text on white background
    </div>
    <div className={this.styles.bottom.name}>
      This should be white text on black background
    </div>
  </div>
}

We need to define the CSS top and bottom classes so that we redefine the custom properties under each of them taking values from different themes. We essentially want to have the following CSS:

.block {
  backgroundColor: var(--bgColor);
  color: var(--fgColor);
}

.block.top {
  --bgColor: while;
  --fgColor: black;
}

.block.bottom {
  --bgColor: black;
  --fgColor: white;
}

We use the block class for optimization purposes and to showcase how Mimcss handles inheriting CSS styles, but it is optional.

Here is how this is done in Mimcss:

class MyStyles extends css.StyleDefinition
{
  theme = this.$use(ColorTheme)

  block = this.$class({
    backgroundColor: this.theme.bgColor,
    color: this.theme.fgColor
  })

  top = this.$class({
    "++": this.block,
    "--": [LightTheme],
  })

  bottom = this.$class({
    "++": this.block,
    "--": [DarkTheme],
  })
}

Just as we did previously, we reference our ColorTheme declaration class. Then we define a helper block CSS class, which sets the foreground and background colors using the custom CSS properties from the theme. Then we define the top and bottom classes and use the "++" property to indicate that they inherit from the block class. Mimcss supports several methods of style inheritance; the "++" property simply appends the name of the referenced class to our class name. That is, the value returned by the styles.top.name is "top block" where we’re combining the two CSS classes (the actual names are randomly generated, so it would be something like "n153 n459").

Then we use the "--" property to set values of the custom CSS variables. Mimcss supports several methods of redefining custom CSS properties in a ruleset; in our case, we just reference a corresponding theme definition class. This causes Mimcss to redefine all custom CSS properties found in the theme class with their corresponding values.

What do you think?

Theming in Mimcss is intentionally based on style definition inheritance. We looked at exactly how this works, where we get the best of both theming worlds: the ability to use alternate stylesheets alongside the ability to swap out CSS property values using an object-oriented approach.

At runtime, Mimcss applies a theme without changing the HTML whatsoever. At build-time, Mimcss leverages the well-tried and easy-to-use class inheritance technique. Please check out the Mimcss documentation for a much deeper dive on the things we covered here. You can also visit the Mimcss Playground where you can explore a number of examples and easily try your own code.

And, of course, tell me what you think of this approach! This has been my go-to solution for theming and I’d like to continue making it stronger based on feedback from developers like yourself.


Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/defining-and-applying-ui-themes-using-the-mimcss-css-in-js-library/feed/ 1 355894
Shadow Roots and Inheritance https://css-tricks.com/shadow-roots-and-inheritance/ https://css-tricks.com/shadow-roots-and-inheritance/#comments Thu, 16 Sep 2021 20:14:20 +0000 https://css-tricks.com/?p=351915 There is a helluva gotcha with styling a <details element, as documented here by Kitty Giraudel. It’s obscure enough that you might never run into it, but if you do, I could see it being very confusing (it would confuse …


Shadow Roots and Inheritance originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There is a helluva gotcha with styling a <details> element, as documented here by Kitty Giraudel. It’s obscure enough that you might never run into it, but if you do, I could see it being very confusing (it would confuse me, at least).

Perhaps you’re aware of the shadow DOM? It’s talked about a lot in terms of web components and comes up when thinking in terms of <svg> and <use>. But <details> has a shadow DOM too:

<details>
  #shadow-root (user-agent)
  <slot name="user-agent-custom-assign-slot" id="details-summary">
    <!-- <summary> reveal -->
  </slot>
  <slot name="user-agent-default-slot" id="details-content">
    <!-- <p> reveal -->
  </slot>

  <summary>System Requirements</summary>
  <p>
    Requires a computer running an operating system. The computer must have some
    memory and ideally some kind of long-term storage. An input device as well
    as some form of output device is recommended.
  </p>
</details>

As Amelia explains, the <summary> is inserted in the first shadow root slot, while the rest of the content (called “light DOM”, or the <p> tag in our case) is inserted in the second slot.

The thing is, none of these slots or the shadow root are matched by the universal selector *, which only matches elements from the light DOM. 

So the <slot> is kind of “in the way” there. That <p> is actually a child of the <slot>, in the end. It’s extra weird, because a selector like details > p will still select it just fine. Presumably, that selector gets resolved in the light DOM and then continues to work after it gets slotted in.

But if you tell a property to inherit, things break down. If you did something like…

<div>
  <p></p>
</div>
div {
  border-radius: 8px;
}
div p {
  border-radius: inherit;
}

…that <p> is going to have an 8px border radius.

But if you do…

<details>
  <summary>Summary</summary>
  <p>Lorem ipsum...</p>
</details>
details {
  border-radius: 8px;
}
details p {
  border-radius: inherit;
}

That <p> is going to be square as a square doorknob. I guess that’s either because you can’t force inheritance through the shadow DOM, or the inherit only happens from the parent which is a <slot>? Whatever the case, it doesn’t work.

To Shared LinkPermalink on CSS-Tricks


Shadow Roots and Inheritance originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/shadow-roots-and-inheritance/feed/ 7 351915
inherit, initial, unset, revert https://css-tricks.com/inherit-initial-unset-revert/ https://css-tricks.com/inherit-initial-unset-revert/#comments Tue, 22 Jun 2021 21:20:20 +0000 https://css-tricks.com/?p=342930 There are four keywords that are valid values for any CSS property (see the title). Of those, day to day, I’d say I see the inherit used the most. Perhaps because it’s been around the longest (I think?) but also …


inherit, initial, unset, revert originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There are four keywords that are valid values for any CSS property (see the title). Of those, day to day, I’d say I see the inherit used the most. Perhaps because it’s been around the longest (I think?) but also because it makes logical sense (“please inherit your value from the next parent up that sets it”). You might see that with an override of a link color, for example.

<footer>
  ©2012 Website — <a href="/contact">Contact</a>
</footer>
/* General site styles */
a {
  color: blue;
}

footer {
  color: white;
}
footer a {
  color: inherit;
}

That’s a decent and elegant way to handle the fact that you want the text and links in the footer to be the same color without having to set it twice.

The others behave differently though…

  • initial will reset the property back to the spec default.
  • unset is weird as heck. For a property that is inherited (e.g. color) it means inherit, and for a property that isn’t inherited (e.g. float) it means initial. That’s a brain twister for me such that I’ve never used it.
  • revert is similarly weird. Same deal for inherited properties, it means inherit. But for non-inherited properties it means to revert to the UA stylesheet. Kinnnnnda useful in that reverting display, for example, won’t make a <p> element display: inline; but it will remain a sensible display: block;.

PPK covered all this in more detail.

I’m glad he found my whining about all this:

Chris Coyier argues we need a new value which he calls default. It reverts to the browser style sheet in all cases, even for inherited properties. Thus it is a stronger version of revert. I agree. This keyword would be actually useful.

Amen. We have four properties for fiddling with the cascade on individual properties, but none that allow us to blast everything back to the UA stylesheet defaults. If we had that, we’d have a very powerful tool for starting fresh with styles on any given element. In one sense: scoped styles!

PPK has a fifth value he thinks would be useful: cascade. The idea (I suppose) is it kinda acts like currentColor except for any property. Sort of like a free variable you don’t have to define that gives you access to what the cascaded value would have been, except you’re going to use it in some other context (like a calculation).


inherit, initial, unset, revert originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/inherit-initial-unset-revert/feed/ 5 342930
Computed Values: More Than Meets the Eye https://css-tricks.com/computed-values-more-than-meets-the-eye/ https://css-tricks.com/computed-values-more-than-meets-the-eye/#comments Wed, 05 Aug 2020 14:37:35 +0000 https://css-tricks.com/?p=312379 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 …


Computed Values: More Than Meets the Eye originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
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.

Screenshot of DevTools window for Chrome in dark mode.
The “Computed” tab is generally located in the right panel of the DevTools interface, like it is shown here in Chrome.

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.

Screenshot of Chrome DevTools in dark mode. DOM elements are on the left and the Computed Properties information is on the right.
The “Computed” tab takes a selected element (1) and displays a list of CSS properties (2) that have been rendered, allowing each one to be expanded (3) to reveal the cascade of inherited values alongside the actual computed value (4) that is currently in use.

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.

Screenshot of Chrome DevTools in dark mode. DOM elements are on the left and the Styles information is on the right.
The “Styles” tab takes a selected element (1) and displays the ruleset (2) that is explicitly declared in the stylesheet, followed by other related rulesets that are included in the cascade (3), including those from other stylesheets (4). Notice how overridden values are crossed out, indicating that another property takes precedence.

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


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.

MDN

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.

Screenshot of Chrome with DevTools open in dark mode on the right. CSS-Tricks is the open site, the elements tab is open in the center, and the Computed Properties values are open on the left.
A button (1) is the current selected element in DevTools (2). The declared width of the button is 100% (3), which computes to 392px (4) when the viewport is in this condition.

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 {} vs section 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.

Screenshot of the specifications section of the color property, taken from the MDN docs. The "Computed value" field is highlighted.
The specifications section of the MDN docs for the color property.

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.

Screenshot of Chrome DevTools open to the Element view with Computed Properties open.
The declared font-size for the main element is 1.2em, which computes to 19.2px.
Screenshot of Chrome DevTools open to the Element view with Computed Properties open.
The declared font-size for the paragraph element is 1.5em, which computes to 28.8px.

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:

Screenshot of the specifications section of the color property, taken from the MDN docs. The "Inherited" field is highlighted.
The specifications section of the MDN docs 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:

Examples of properties that do not (but which you can force to inherit with the inherit keyword):


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


Computed Values: More Than Meets the Eye originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/computed-values-more-than-meets-the-eye/feed/ 6 312379
Using Custom Property “Stacks” to Tame the Cascade https://css-tricks.com/using-custom-property-stacks-to-tame-the-cascade/ https://css-tricks.com/using-custom-property-stacks-to-tame-the-cascade/#comments Mon, 22 Jun 2020 14:47:49 +0000 https://css-tricks.com/?p=312969 Since the inception of CSS in 1994, the cascade and inheritance have defined how we design on the web. Both are powerful features but, as authors, we’ve had very little control over how they interact. Selector specificity and source order …


Using Custom Property “Stacks” to Tame the Cascade originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Since the inception of CSS in 1994, the cascade and inheritance have defined how we design on the web. Both are powerful features but, as authors, we’ve had very little control over how they interact. Selector specificity and source order provide some minimal “layering” control, without a lot of nuance — and inheritance requires an unbroken lineage. Now, CSS Custom Properties allow us to manage and control both cascade and inheritance in new ways.

I want to show you how I’ve used Custom Property “stacks” to solve some of the common issues people face in the cascade: from scoped component styles, to more explicit layering of intents.

A quick intro to Custom Properties

The same way browsers have defined new properties using a vendor prefix like -webkit- or -moz-, we can define our own Custom Properties with an “empty” -- prefix. Like variables in Sass or JavaScript, we can use them to name, store, and retrieve values — but like other properties in CSS, they cascade and inherit with the DOM.

/* Define a custom property */
html {
  --brand-color: rebeccapurple;
}

In order to access those captured values, we use the var() function. It has two parts: first the name of our custom property, and then a fallback in case that property is undefined:

button {
  /* use the --brand-color if available, or fall back to deeppink */
  background: var(--brand-color, deeppink);
}

This is not a support fallback for old browsers. If a browser doesn’t understand custom properties, it will ignore the entire var() declaration. Instead, this is a built-in way of handling undefined variables, similar to a font stack defining fallback font families when one is unavailable. If we don’t provide a fallback, the default is unset.

Building variable “stacks”

This ability to define a fallback is similar to “font stacks” used on the font-family property. If the first family is unavailable, the second will be used, and so on. The var() function only accepts a single fallback, but we can nest var() functions to create custom-property fallback “stacks” of any size:

button {
  /* try Consolas, then Menlo, then Monaco, and finally monospace */
  font-family: Consolas, Menlo, Monaco, monospace;

  /* try --state, then --button-color, then --brand-color, and finally deeppink */
  background: var(--state, var(--button-color, var(--brand-color, deeppink)));
}

If that nested syntax for stacked properties looks bulky, you can use a pre-processor like Sass to make it more compact.

That single-fallback limitation is required to support fallbacks with a comma inside them — like font stacks or layered background images:

html {
  /* The fallback value is "Helvetica, Arial, sans-serif" */
  font-family: var(--my-font, Helvetica, Arial, sans-serif);
}

Defining “scope”

CSS selectors allow us to drill down into the HTML DOM tree, and style elements anywhere on the page, or elements in a particular nested context.

/* all links */
a { color: slateblue; }

/* only links inside a section */
section a { color: rebeccapurple; }

/* only links inside an article */
article a { color: deeppink; }

That’s useful, but it doesn’t capture the reality of “modular” object-oriented or component-driven styles. We might have multiple articles and asides, nested in various configurations. We need a way to clarify which context, or scope, should take precedence when they overlap.

Proximity scopes

Let’s say we have a .light theme and a .dark theme. We can use those classes on the root <html> element to define a page-wide default, but we can also apply them to specific components, nested in various ways:

Each time we apply one of our color-mode classes, the background and color properties are reset, then inherited by nested headings and paragraphs. In our main context, colors inherit from the .light class, while the nested heading and paragraph inherit from the .dark class. Inheritance is based on direct lineage, so the nearest ancestor with a defined value will take precedence. We call that proximity.

Proximity matters for inheritance, but it has no impact on selectors, which rely on specificity. That becomes a problem if we want to style something inside the dark or light containers.

Here I’ve attempted to define both light and dark button variants. Light mode buttons should be rebeccapurple with white text so they stand out, and dark mode buttons should be plum with black text. We’re selecting the buttons directly based on a light and dark context, but it doesn’t work:

Some of the buttons are in both contexts, with both .light and .dark ancestors. What we want in that case is for the closest theme to take over (inheritance proximity behavior), but what we get instead is the second selector overriding the first (cascade behavior). Since the two selectors have the same specificity, source order determines the winner.

Custom Properties and proximity

What we need here is a way to inherit these properties from the theme, but only apply them to specific children. Custom Properties make that possible! We can define values on the light and dark containers, while only using their inherited values on nested elements, like our buttons.

We’ll start by setting up the buttons to use custom properties, with a fallback “default” value, in case those properties are undefined:

button {
  background: var(--btn-color, rebeccapurple);
  color: var(--btn-contrast, white);
}

Now we can set those values based on context, and they will scope to the appropriate ancestor based on proximity and inheritance:

.dark {
  --btn-color: plum;
  --btn-contrast: black;
}

.light {
  --btn-color: rebeccapurple;
  --btn-contrast: white;
}

As an added bonus, we’re using less code overall, and one unified button definition:

I think of this as creating an API of available parameters for the button component. Sara Soueidan and Lea Verou have both covered this well in recent articles.

Component ownership

Sometimes proximity isn’t enough to define scope. When JavaScript frameworks generate “scoped styles” they are establishing specific object-element ownership. A “tab layout” component owns the tabs themselves, but not the content behind each tab. This is also what the BEM convention attempts to capture in complex .block__element class names.

Nicole Sullivan coined the term “donut scope” to talk about this problem back in 2011. While I’m sure she has more recent thoughts on the issue, the fundamental problem hasn’t changed. Selectors and specificity are great for describing how we build detailed styles over top of broad patterns, but they don’t convey a clear sense of ownership.

We can use custom property stacks to help solve this problem. We’ll start by creating “global” properties on the <html> element that are for our default colors:

html {
  --background--global: white;
  --color--global: black;
  --btn-color--global: rebeccapurple;
  --btn-contrast--global: white;
}

That default global theme is now available anywhere we want to refer to it. We’ll do that with a data-theme attribute that applies our foreground and background colors. We want the global values to provide a default fallback, but we also want the option to override with a specific theme. That’s where “stacks” come in:

[data-theme] {
  /* If there's no component value, use the global value */
  background: var(--background--component, var(--background--global));
  color: var(--color--component, var(--color--global));
}

Now we can define an inverted component by setting the *--component properties as a reverse of the global properties:

[data-theme='invert'] {
  --background--component: var(--color--global);
  --color--component: var(--background--global);
}

But we don’t want those settings to inherit beyond the donut of ownership, so we reset those values to initial (undefined) on every theme. We’ll want to do this at a lower specificity, or earlier in the source order, so it provides a default that each theme can override:

[data-theme] {
  --background--component: initial;
  --color--component: initial;
}

The initial keyword has a special meaning when used on custom properties, reverting them to a Guaranteed-Invalid state. That means rather than being passed along to set background: initial or color: initial, the custom property becomes undefined, and we fallback to the next value in our stack, the global settings.

We can do the same thing with our buttons, and then make sure to apply data-theme to each component. If no specific theme is given, each component will default to the global theme:

Defining “origins”

The CSS cascade is a series of filtering layers used to determine what value should take precedence when multiple values are defined on the same property. We most often interact with the specificity layers, or the final layering based on source-order — but the first layer of cascade is the “origin” of a style. The origin describes where a style came from — often the browser (defaults), the user (preferences), or the author (that’s us).

By default, author styles override user preferences, which override browser defaults. That changes when anyone applies `!important` to a style, and the origins reverse: browser `!important` styles have the highest origin, then important user preferences, then our author important styles, above all the normal layers. There are a few additional origins, but we won’t go into them here.

When we create custom property “stacks,” we’re building a very similar behavior. If we wanted to represent existing origins as a stack of custom properties, it would look something like this:

.origins-as-custom-properties {
  color: var(--browser-important, var(--user-important, var(--author-important, var(--author, var(--user, var(--browser))))));
}

Those layers already exist, so there’s no reason to recreate them. But we’re doing something very similar when we layer our “global” and “component” styles above — creating a “component” origin layer that overrides our “global” layer. That same approach can be used to solve various layering issues in CSS, which can’t always be described by specificity:

  • Override » Component » Theme » Default
  • Theme » Design system or framework
  • State » Type » Default

Let’s look at some buttons again. We’ll need a default button style, a disabled state, and various button “types,” like danger, primary and secondary. We wan’t the disabled state to always override the type variations, but selectors don’t capture that distinction:

But we can define a stack that provides both “type” and “state” layers in the order that we want them prioritized:

button {
  background: var(--btn-state, var(--btn-type, var(--btn-default)));
}

Now when we set both variables, the state will always take precedence:

I’ve used this technique to create a Cascading Colors framework that allows custom theming based on layering:

  • Pre-defined theme attributes in the HTML
  • User color preferences
  • Light and dark modes
  • Global theme defaults

Mix and match

These approaches can be taken to an extreme, but most day-to-day use-cases can be handled with two or three values in a stack, often using a combination of the techniques above:

  • A variable stack to define the layers
  • Inheritance to set them based on proximity and scope
  • Careful application of the `initial` value to remove nested elements from a scope

We’ve been using these custom property “stacks” on our projects at OddBird. We’re still discovering as we go, but they’ve already been helpful in solving problems that were difficult using only selectors and specificity. With custom properties, we don’t have to fight the cascade or inheritance. We can capture and leverage them, as-intended, with more control over how they should apply in each instance. To me, that’s a big win for CSS — especially when developing style frameworks, tools, and systems.


Using Custom Property “Stacks” to Tame the Cascade originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/using-custom-property-stacks-to-tame-the-cascade/feed/ 2 312969
What does “revert” do in CSS? https://css-tricks.com/what-does-revert-do-in-css/ https://css-tricks.com/what-does-revert-do-in-css/#comments Tue, 28 Jan 2020 00:51:26 +0000 https://css-tricks.com/?p=302263 Miriam Suzanne explains in a Mozilla Developer video on the subject.

The revert value in CSS “resets” a property back to it’s inherited value, only going as far back as the UA stylesheet. That’s critical, as it won’t reset a <p


What does “revert” do in CSS? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Miriam Suzanne explains in a Mozilla Developer video on the subject.

The revert value in CSS “resets” a property back to it’s inherited value, only going as far back as the UA stylesheet. That’s critical, as it won’t reset a <p> to inline, for example, because a <p> is a block-level element as set in the UA stylesheet.

So if we had this HTML:

<p>Lorem, ipsum dolor.</p>
<p class="alt">Fugit, id vel.</p>

And CSS:

p {
  color: red;
}
.alt {
  color: revert;
}

Both paragraphs would be selected by the p selector, making them red, but the class selector on the second paragraph has higher specificity, so the color: revert; wins, changing the text back to black (the UA default).

But, the color property cascades, so if we had:

<div class="parent">
  <p>Lorem, ipsum dolor.</p>
  <p class="alt">Fugit, id vel.</p>
</div>
.parent {
  color: blue;
}
p {
  color: red;
}
.alt {
  color: revert;
}

The second paragraph would be blue because revert forces it to take its color from inheritance.

The revert value is fairly new, supported in Firefox and Safari, but not yet in Chrome-world. We’ve already got a couple of related keywords that work on any property which are meant to help control inheritance and reset values.

The difference is small, but important: unset allows inheritance, while initial does not.

Miriam makes the case that revert is actually the most useful of them because it “takes user and user-agent styles into consideration.”

I don’t disagree. But (and I hate to say this) I do think we need a fourth option, one that has the forcing power of initial, but the UA stylesheet respect of revert. Something like…

.button {
  all: default; /* Not real! */

  /* New styles starting from UA base */
}

These keywords work with any property, but I think using all is the most compelling. It’s a way to wipe out all styles on an element to start with a blank slate. That said, none of the three options is quite good enough for that use case. The unset and revert values aren’t good enough because they still allow inheritance and so doesn’t wipe out styles well enough. The initial value is too strong in that it wipes out defaults you might not expect, like making a <div> inline instead of block.


What does “revert” do in CSS? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/what-does-revert-do-in-css/feed/ 7 302263
The Kind of Development I Like https://css-tricks.com/the-kind-of-development-i-like/ https://css-tricks.com/the-kind-of-development-i-like/#comments Mon, 18 Nov 2019 15:42:15 +0000 https://css-tricks.com/?p=297574 I’m turning 40 next year (yikes!) and even though I’ve been making websites for over 25 years, I feel like I’m finally beginning to understand the kind of development I like. Expectedly, these are not new revelations and my views …


The Kind of Development I Like originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’m turning 40 next year (yikes!) and even though I’ve been making websites for over 25 years, I feel like I’m finally beginning to understand the kind of development I like. Expectedly, these are not new revelations and my views can be summed up by two older Computer Science adages that pre-date my career.

  1. Composition over inheritance
  2. Convention over configuration

Allow me to take you on a short journey. In modern component-driven web development, I often end up with or see structures like this:

<ComponentA>
  <ComponentB>
    <ComponentC />
  </ComponentB>
</ComponentA>

Going down this route is a system where everything is nested child components and props or data are passed down from parent components. It works, but for me, it zaps the fun out of programming. It feels more like plumbing than programming.

Seeing Mozilla’s new ECSY framework targeted at 2D games and 3D virtual reality scenes, I immediately found myself gravitating towards its programming model where Components chain their behaviors onto objects called Entities.

EntityA
  .addComponent('ComponentA')
  .addComponent('ComponentB')

Hey! That looks like a chained jQuery method. I like this and not just for nostalgia’s sake. It’s the “composition” of functionality that I like. I know CSS is fraught with inheritance problems, but it reminds me of adding well-formed CSS classes. I gravitate towards that. Knowing I personally favor composition actually helped me resolve some weird inconsistent feelings on why I genuinely like React Hooks (composition) even though I’m not particularly fond of the greater React ecosystem (inheritance).

I think I must confess and apologize for a lot of misplaced anger at React. As a component system, it’s great. I used it on a few projects but never really bonded with it. I think I felt shame that I didn’t enjoy this very popular abstraction and felt out of sync with popular opinion. Now I think I understand more about why.

I should apologize to webpack too. As a bundling and tree shaking tool, it does a great job. It’s even better when all the configuration is hidden inside tools like Angular CLI and Nuxt. My frustrations were real, but as I learn more about myself, I realized it might be something else…

My frustrations with modern web development have continued to tumble downwards in levels of abstraction. I now think about npm and wonder if it’s somewhat responsible for some of the pain points of modern web development today. Fact is, npm is a server-side technology that we’ve co-opted on the client and I think we’re feeling those repercussions in the browser.

The Unix Philosophy encourages us to write small micro libraries that do one thing and do it well. The Node.js Ecosystem did this in spades. This works great on the server where importing a small file has a very small cost. On the client, however, this has enormous costs. So we build processes and tools to bundle these 46,000 scripts together. But that obfuscates the end product. It’s not uncommon that a site could be using fetch, axios, and bluebird all at the same time and all of lodash just to write a forEach loop.

In an “npm install your problems away” world, I feel like we do less programming and more configuring things we installed from the Internet. As dependencies grow in features and become more flexible, they allow you to configure some of the option flags. As a one-off, configs are a great feature. But cumulatively, even on a “simple” project, I can find myself managing and battling over a half dozen config files. One day while swimming in a sea of JSON configs it dawned on me: I don’t like configuration.

“Convention over configuration” was a set of ideals popularized by David Heinemeier Hansson (@DHH) and it guided a lot of the design of Ruby on Rails. While the saying has waned in popularity some, I think it sums up the kind of development I like, especially when frameworks are involved. Frameworks should try to be a collection of best practices, to save others from having to overthink decisions. I’ve said it before, but I think Nuxt does this really well. When I step into a system of predefined conventions and minor configuration, I’m much happier than the opposite system of no conventions and lots of configuration.

It’s a little weird to be turning 40 and discovering so much about the job I do on a daily basis. But it’s nice to have found some vocabulary and principles for what I like about development. Your list of things you like may be different than mine and that’s a good thing. I’d love to know more about the kind of development you like. What do you like to build? What are you optimizing for? What is your definition of success?


The Kind of Development I Like originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-kind-of-development-i-like/feed/ 5 297574
CSS Basics: The Second “S” in CSS https://css-tricks.com/css-basics-second-s-css/ https://css-tricks.com/css-basics-second-s-css/#comments Wed, 14 Feb 2018 14:14:24 +0000 http://css-tricks.com/?p=266529 CSS is an abbreviation for Cascading Style Sheets.

While most of the discussion about CSS on the web (or even here on CSS-Tricks) is centered around writing styles and how the cascade affects them, what we don’t talk a whole …


CSS Basics: The Second “S” in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS is an abbreviation for Cascading Style Sheets.

While most of the discussion about CSS on the web (or even here on CSS-Tricks) is centered around writing styles and how the cascade affects them, what we don’t talk a whole lot about is the sheet part of the language. So let’s give that lonely second “S” a little bit of the spotlight and understand what we mean when we say CSS is a style sheet.

The Sheet Contains the Styles

The cascade describes how styles interact with one another. The styles make up the actual code. Then there’s the sheet that contains that code. Like a sheet of paper that we write on, the “sheet” of CSS is the digital file where styles are coded.

If we were to illustrate this, the relationship between the three sort of forms a cascade:

The sheet holds the styles.

There can be multiple sheets all continuing multiple styles all associated with one HTML document. The combination of those and the processes of figuring out what styles take precedence to style what elements is called the cascade (That first “C” in CSS).

The Sheet is a Digital File

The sheet is such a special thing that it’s been given its own file extension: .css. You have the power to create these files on your own. Creating a CSS file can be done in any text editor. They are literally text files. Not “rich text” documents or Word documents, but plain ol’ text.

If you’re on Mac, then you can fire up TextEdit to start writing CSS. Just make sure it’s in “Plain Text” mode.

If you’re on Windows, the default Notepad app is the equivalent. Heck, you can type styles in just about any plain text editor to write CSS, even if that’s not what it says it was designed to do.

Whatever tool you use, the key is to save your document as a .css file. This can usually be done by simply add that to your file name when saving. Here’s how that looks in TextEdit:

Seriously, the choice of which text editor to use for writing CSS is totally up to you. There are many, many to choose from, but here are a few popular ones:

You might reach for one of those because they’ll do handy things for you like syntax highlight the code (colorize different parts to help it be easier to understand what is what).

Hey look I made some files completely from scratch with my text editor:

Those files are 100% valid in any web browser, new or old. We’ve quite literally just made a website.

The Sheet is Linked Up to the HTML

We do need to connect the HTML and CSS though. As in make sure the styles we wrote in our sheet get loaded onto the web page.

A webpage without CSS is pretty barebones:

See the Pen Style-less Webpage by Geoff Graham (@geoffgraham) on CodePen.

Once we link up the CSS file, voila!

See the Pen Webpage With Styles by Geoff Graham (@geoffgraham) on CodePen.

How did that happen? if you look at the top of any webpage, there’s going to be a <head> tag that contains information about the HTML document:

<!DOCTYPE html>
<html>
	<head>
		<!-- a bunch of other stuff -->
	</head>

	<body>
		<!-- the page content -->
	</body>

</html>

Even though the code inside the <head> might look odd, there is typically one line (or more, if we’re using multiple stylesheets) that references the sheet. It looks something like this:

<head>
  <link rel="stylesheet" type="text/css" href="styles.css" />
</head>

This line tells the web browser as it reads this HTML file:

  1. I’d like to link up a style sheet
  2. Here’s where it is located

You can name the sheet whatever you want:

  • styles.css
  • global.css
  • seriously-whatever-you-want.css

The important thing is to give the correct location of the CSS file, whether that’s on your web server, a CDN or some other server altogether.

Here are a few examples:

<head>
  <!-- CSS on my server in the top level directory -->
  <link rel="stylesheet" type="text/css" href="styles.css">

  <!-- CSS on my server in another directory -->
  <link rel="stylesheet" type="text/css" href="/css/styles.css">

  <!-- CSS on another server -->
  <link rel="stylesheet" type="text/css" href="https://some-other-site/path/to/styles.css">
</head>

The Sheet is Not Required for HTML

You saw the example of a barebones web page above. No web page is required to use a stylesheet.

Also, we can technically write CSS directly in the HTML using the HTML style attribute. This is called inline styling and it goes a little something like this if you imagine you’re looking at the code of an HTML file:

<h1 style="font-size: 24px; line-height: 36px; color: #333333">A Headline</h1>
<p style="font-size: 16px; line-height: 24px; color: #000000;">Some paragraph content.</p>
<!-- and so on -->

While that’s possible, there are three serious strikes against writing styles this way:

  1. If you decide to use a stylesheet later, it is extremely difficult to override inline styles with the styles in the HTML. Inline styles take priority over styles in a sheet.
  2. Maintaining all of those styles is tough if you need to make a “quick” change and it makes the HTML hard to read.
  3. There’s something weird about saying we’re writing CSS inline when there really is no cascade or sheet. All we’re really writing are styles.

There is a second way to write CSS in the HTML and that’s directly in the <head> in a <style> block:

<head>
	<style>
  	h1 {
  		color: #333;
  		font-size: 24px;
  		line-height: 36px;
  	}

  	p {
  		color: #000;
  		font-size: 16px;
  		line-height: 24px;
  	}
	</style>
</head>

That does indeed make the HTML easier to read, already making it better than inline styling. Still, it’s hard to manage all styles this way because it has to be managed on each and every webpage of a site, meaning one “quick” change might have to be done several times, depending on how many pages we’re dealing with.

An external sheet that can be called once in the <head> is usually your best bet.

The Sheet is Important

I hope that you’re starting to see the importance of the sheet by this point. It’s a core part of writing CSS. Without it, styles would be difficult to manage, HTML would get cluttered, and the cascade would be nonexistent in at least one case.

The sheet is the core component of CSS. Sure, it often appears to play second fiddle to the first “S” but perhaps that’s because we all have an quiet understanding of its importance.

Leveling Up

Now that you’re equipped with information about stylesheets, here are more resources you jump into to get a deeper understanding for how CSS behaves:


CSS Basics: The Second “S” in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-basics-second-s-css/feed/ 5 266529