Miriam Suzanne – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Mon, 03 Jun 2024 17:18:37 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 Miriam Suzanne – CSS-Tricks https://css-tricks.com 32 32 45537868 A Complete Guide to CSS Cascade Layers https://css-tricks.com/css-cascade-layers/ https://css-tricks.com/css-cascade-layers/#comments Mon, 21 Feb 2022 15:40:35 +0000 https://css-tricks.com/?p=363766 This is your complete guide to CSS cascade layers, a CSS feature that allows us to define explicit contained layers of specificity, so that we have full control over which styles take priority in a project without relying on specificity …


A Complete Guide to CSS Cascade Layers originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is your complete guide to CSS cascade layers, a CSS feature that allows us to define explicit contained layers of specificity, so that we have full control over which styles take priority in a project without relying on specificity hacks or !important. This guide is intended to help you fully understand what cascade layers are for, how and why you might choose to use them, the current levels of support, and the syntax of how you use them.

Table of Contents

Quick example

/* establish a layer order up-front, from lowest to highest priority */
@layer reset, defaults, patterns, components, utilities, overrides;

/* import stylesheets into a layer (dot syntax represents nesting) */
@import url('framework.css') layer(components.framework);

/* add styles to layers */
@layer utilities {
  /* high layer priority, despite low specificity */
  [data-color='brand'] { 
    color: var(--brand, rebeccapurple);
  }
}

@layer defaults {
  /* higher specificity, but lower layer priority */
  a:any-link { color: maroon; }
}

/* un-layered styles have the highest priority */
a {
  color: mediumvioletred;
}

Introduction: what are cascade layers?

CSS Cascade Layers are intended to solve tricky problems in CSS. Let’s take a look at the main problem and how cascade layers aim to solve it.

Problem: Specificity conflicts escalate

Many of us have been in situations where we want to override styles from elsewhere in our code (or a third-party tool), due to conflicting selectors. And over the years, authors have developed a number of “methodologies” and “best practices” to avoid these situations — such as “only using a single class” for all selectors. These rules are usually more about avoiding the cascade, rather than putting it to use.

Managing cascade conflicts and selector specificity has often been considered one of the harder — or at least more confusing — aspects of CSS. That may be partly because few other languages rely on a cascade as their central feature, but it’s also true that the original cascade relies heavily on heuristics (an educated-guess or assumption built into the code) rather than providing direct and explicit control to web authors.

Selector specificity, for example — our primary interaction with the cascade — is based on the assumption that more narrowly targeted styles (like IDs that are only used once) are likely more important than more generic and reusable styles (like classes and attributes). That is to say: how specific the selector is. That’s a good guess, but it’s not a totally reliable rule, and that causes some issues:

  • It combines the act of selecting elements, with the act of prioritizing rule-sets.
  • The simplest way to ‘fix’ a conflict with specificity is to escalate the problem by adding otherwise unnecessary selectors, or (gasp) throwing the !important hand-grenade.
.overly#powerful .framework.widget {
  color: maroon;
}

.my-single_class { /* add some IDs to this ??? */
  color: rebeccapurple; /* add !important ??? */
}

Solution: cascade layers provide control

Cascade layers give CSS authors more direct control over the cascade so we can build more intentionally cascading systems without relying as much on heuristic assumptions that are tied to selection.

Using the @layer at-rule and layered @imports, we can establish our own layers of the cascade — building from low-priority styles like resets and defaults, through themes, frameworks, and design systems, up to highest-priority styles, like components, utilities, and overrides. Specificity is still applied to conflicts within each layer, but conflicts between layers are always resolved by using the higher-priority layer styles.

@layer framework {
  .overly#powerful .framework.widget {
    color: maroon;
  }
}

@layer site {
  .my-single_class {
    color: rebeccapurple;
  }
}

These layers are ordered and grouped so that they don’t escalate in the same way that specificity and importance can. Cascade layers aren’t cumulative like selectors. Adding more layers doesn’t make something more important. They’re also not binary like importance — suddenly jumping to the top of a stack — or numbered like z-index, where we have to guess a big number (z-index: 9999999?). In fact, by default, layered styles are less important than un-layered styles.

@layer defaults {
  a:any-link { color: maroon; }
}

/* un-layered styles have the highest priority */
a {
  color: mediumvioletred;
}

Where do layers fit in the cascade?

The cascade is a series of steps (an algorithm) for resolving conflicts between styles.

html { --button: teal; }
button { background: rebeccapurple !important; }
.warning { background: maroon; }
<button class="warning" style="background: var(--button);">
  what color background?
</button>

With the addition of cascade layers, those steps are:

Illustration of the various specificity levels of the CSS cascade and where CSS Cascade Layers fit in it.

Selector specificity is only one small part of the cascade, but it’s also the step we interact with most, and is often used to refer more generally to overall cascade priority. People might say that the !important flag or the style attribute “adds specificity” — a quick way of expressing that the style becomes higher priority in the cascade. Since cascade layers have been added directly above specificity, it’s reasonable to think about them in a similar way: one step more powerful than ID selectors.

However, CSS Cascade Layers also make it more essential that we fully understand the role of !important in the cascade — not just as a tool for “increasing specificity” but as a system for balancing concerns.

!important origins, context, and layers are reversed!

As web authors, we often think of !important as a way of increasing specificity, to override inline styles or highly specific selectors. That works OK in most cases (if you’re OK with the escalation) but it leaves out the primary purpose of importance as a feature in the overall cascade.

Importance isn’t there to simply increase power — but to balance the power between various competing concerns.

Important origins

It all starts with origins, where a style comes from in the web ecosystem. There are three basic origins in CSS:

  • The browser (or user agent)
  • The user (often via browser preferences)
  • Web authors (that’s us!)

Browsers provide readable defaults for all the elements, and then users set their preferences, and then we (authors) provide the intended design for our web pages. So, by default, browsers have the lowest priority, user preferences override the browser defaults, and we’re able to override everyone.

But the creators of CSS were very clear that we should not actually have the final word:

If conflicts arise the user should have the last word, but one should also allow the author to attach style hints.

— Håkon Lie (emphasis added)

So importance provides a way for the browser and users to re-claim their priority when it matters most. When the !important flag is added to a style, three new layers are created — and the order is reversed!

  1. !important browser styles (most powerful)
  2. !important user preferences
  3. !important author styles
  4. normal author styles
  5. normal user preferences
  6. normal browser styles (least powerful)

For us, adding !important doesn’t change much — our important styles are pretty close to our normal styles — but for the browser and user it’s a very powerful tool for regaining control. Browser default style sheets include a number of important styles that it would be impossible for us to override, such as:

iframe:fullscreen {
  /* iframes in full-screen mode don't show a border. */
  border: none !important;
  padding: unset !important;
}

While most of the popular browsers have made it difficult to upload actual user stylesheets, they all offer user preferences: a graphic interface for establishing specific user styles. In that interface, there is always a checkbox available for users to choose if a site is allowed to override their preferences or not. This is the same as setting !important in a user stylesheet:

Screenshot of user font preferences.

Important context

The same basic logic is applied to context in the cascade. By default, styles from the host document (light DOM) override styles from an embedded context (shadow DOM). However, adding !important reverses the order:

  1. !important shadow context (most powerful)
  2. !important host context
  3. normal host context
  4. normal shadow context (least powerful)

Important styles that come from inside a shadow context override important styles defined by the host document. Here’s an odd-bird custom element with some styles written in the element template (shadow DOM), and some styles in the host page (light DOM) stylesheet:

Both color declarations have normal importance, and so the host page mediumvioletred takes priority. But the font-family declarations are flagged !important, giving advantage to the shadow-context, where fantasy is defined.

Important layers

Cascade layers work the same way as both origins and context, with the important layers in reverse-order. The only difference is that layers make that behavior much more noticeable.

Once we start using cascade layers, we will need to be much more cautious and intentional about how we use !important. It’s no longer a quick way to jump to the top of the priorities — but an integrated part of our cascade layering; a way for lower layers to insist that some of their styles are essential.

Since cascade layers are customizable, there’s no pre-defined order. But we can imagine starting with three layers:

  1. utilities (most powerful)
  2. components
  3. defaults (least powerful)

When styles in those layers are marked as important, they would generate three new, reversed important layers:

  1. !important defaults (most powerful)
  2. !important components
  3. !important utilities
  4. normal utilities
  5. normal components
  6. normal defaults (least powerful)

In this example, the color is defined by all three normal layers, and the utilities layer wins the conflict, applying the maroon color, as the utilities layer has a higher priority in @layers. But notice that the text-decoration property is marked !important in both the defaults and components layers, where important defaults take priority, applying the underline declared by defaults:

Establishing a layer order

We can create any number of layers and name them or group them in various ways. But the most important thing to do is to make sure our layers are applied in the right order of priority.

A single layer can be used multiple times throughout the codebase — cascade layers stack in the order they first appear. The first layer encountered sits at the bottom (least powerful), and the last layer at the top (most powerful). But then, above that, un-layered styles have the highest priority:

@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 { a { color: yellow; } }
/* un-layered */ a { color: green; }
  1. un-layered styles (most powerful)
  2. layer-3
  3. layer-2
  4. layer-1 (least powerful)

Then, as discussed above, any important styles are applied in a reverse order:

@layer layer-1 { a { color: red !important; } }
@layer layer-2 { a { color: orange !important; } }
@layer layer-3 { a { color: yellow !important; } }
/* un-layered */ a { color: green !important; }
  1. !important layer-1 (most powerful)
  2. !important layer-2
  3. !important layer-3
  4. !important un-layered styles
  5. normal un-layered styles
  6. normal layer-3
  7. normal layer-2
  8. normal layer-1 (least powerful)

Layers can also be grouped, allowing us to do more complicated sorting of top-level and nested layers:

@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 {
  @layer sub-layer-1 { a { color: yellow; } }
  @layer sub-layer-2 { a { color: green; } }
  /* un-nested */ a { color: blue; }
}
/* un-layered */ a { color: indigo; }
  1. un-layered styles (most powerful)
  2. layer-3
    1. layer-3 un-nested
    2. layer-3 sub-layer-2
    3. layer-3 sub-layer-1
  3. layer-2
  4. layer-1 (least powerful)

Grouped layers always stay together in the final layer order (for example, sub-layers of layer-3 will all be next to each other), but this otherwise behaves the same as if the list was “flattened” — turning this into a single six-item list. When reversing !important layer order, the entire list flattened is reversed.

But layers don’t have to be defined once in a single location. We give them names so that layers can be defined in one place (to establish layer order), and then we can append styles to them from anywhere:

/* describe the layer in one place */
@layer my-layer;

/* append styles to it from anywhere */
@layer my-layer { a { color: red; } }

We can even define a whole ordered list of layers in a single declaration:

@layer one, two, three, four, five, etc;

This makes it possible for the author of a site to have final say over the layer order. By providing a layer order up-front, before any third party code is imported, the order can be established and rearranged in one place without worrying about how layers are used in any third-party tool.

Syntax: Working with cascade layers

Let’s take a look at the syntax!

Order-setting @layer statements

Since layers are stacked in the order they are defined, it’s important that we have a tool for establishing that order all in one place!

We can use @layer statements to do that. The syntax is:

@layer <layer-name>#;

That hash (#) means we can add as many layer names as we want in a comma-separated list:

@layer reset, defaults, framework, components, utilities;

That will establish the layer order:

  1. un-layered styles (most powerful)
  2. utilities
  3. components
  4. framework
  5. defaults
  6. reset (least powerful)

We can do this as many times as we want, but remember: what matters is the order each name first appears. So this will have the same result:

@layer reset, defaults, framework;
@layer components, defaults, framework, reset, utilities;

The ordering logic will ignore the order of reset, defaults, and framework in the second @layer rule because those layers have already been established. This @layer list syntax doesn’t add any special magic to the layer ordering logic: layers are stacked based on the order in which the layers first appear in your code. In this case, reset appears first in the first @layer list. Any @layer statement that comes later can only append layer names to the list, but can’t move layers that already exist. This ensures that you can always control the final overall layer order from one location — at the very start of your styles.

These layer-ordering statements are allowed at the top of a stylesheet, before the @import rule (but not between imports). We highly recommend using this feature to establish all your layers up-front in a single place so you always know where to look or make changes.

Block @layer rules

The block version of the @layer rule only takes a single layer name, but then allows you to add styles to that layer:

@layer <layer-name> {
  /* styles added to the layer */
}

You can put most things inside an @layer block — media queries, selectors and styles, support queries, etc. The only things you can’t put inside a layer block are things like charset, imports, and namespaces. But don’t worry, there is a syntax for importing styles into a layer.

If the layer name hasn’t been established before, this layer rule will add it to the layer order. But if the name has been established, this allows you to add styles to existing layers from anywhere in the document — without changing the priority of each layer.

If we’ve established our layer-order up-front with the layer statement rule, we no longer need to worry about the order of these layer blocks:

/* establish the order up-front */
@layer defaults, components, utilities;

/* add styles to layers in any order */
@layer utilities {
  [hidden] { display: none; }
}

/* utilities will override defaults, based on established order */
@layer defaults {
  * { box-sizing: border-box; }
  img { display: block; }
}

Grouping (nested) layers

Layers can be grouped, by nesting layer rules:

@layer one {
  /* sorting the sub-layers */
  @layer two, three;

  /* styles ... */
  @layer three { /* styles ... */ }
  @layer two { /* styles ... */ }
}

This generates grouped layers that can be represented by joining the parent and child names with a period. That means the resulting sub-layers can also be accessed directly from outside the group:

/* sorting nested layers directly */
@layer one.two, one.three;

/* adding to nested layers directly */
@layer one.three { /* ... */ }
@layer one.two { /* ... */ }

The rules of layer-ordering apply at each level of nesting. Any styles that are not further nested are considered “un-layered” in that context, and have priority over further nested styles:

@layer defaults {
  /* un-layered defaults (higher priority) */
  :any-link { color: rebeccapurple; }

  /* layered defaults (lower priority) */
  @layer reset {
    a[href] { color: blue; }
  }
}

Grouped layers are also contained within their parent, so that the layer order does not intermix across groups. In this example, the top level layers are sorted first, and then the layers are sorted within each group:

@layer reset.type, default.type, reset.media, default.media;

Resulting in a layer order of:

  • un-layered (most powerful)
  • default group
    • default un-layered
    • default.media
    • default.type
  • reset group
    • reset un-layered
    • reset.media
    • reset.type

Note that layer names are also scoped so that they don’t interact or conflict with similarly-named layers outside their nested context. Both groups can have distinct media sub-layers.

This grouping becomes especially important when using @import or <link> to layer entire stylesheets. A third-party tool, like Bootstrap, could use layers internally — but we can nest those layers into a shared bootstrap layer-group on import, to avoid potential layer-naming conflicts.

Entire stylesheets can be added to a layer using the new layer() function syntax with @import rules:

/* styles imported into to the <layer-name> layer */
@import url('example.css') layer(<layer-name>);

There is also a proposal to add a layer attribute in the HTML <link> element — although this is still under development, and not yet supported anywhere. This can be used to import third-party tools or component libraries, while grouping any internal layers together under a single layer name — or as a way of organizing layers into distinct files.

Anonymous (un-named) layers

Layer names are helpful as they allow us to access the same layer from multiple places for sorting or combining layer blocks — but they are not required.

It’s possible to create anonymous (un-named) layers using the block layer rule:

@layer { /* ... */ }
@layer { /* ... */ }

Or using the import syntax, with a layer keyword in place of the layer() function:

/* styles imported into to a new anonymous layer */
@import url('../example.css') layer;

Each anonymous layer is unique, and added to the layer order where it is encountered. Anonymous layers can’t be referenced from other layer rules for sorting or appending more styles.

These should probably be used sparingly, but there might be a few use cases:

  • Projects could ensure that all styles for a given layer are required to be located in a single place.
  • Third-party tools could “hide” their internal layering inside anonymous layers so that they don’t become part of the tool’s public API.

Reverting values to the previous layer

There are several ways that we can use to “revert” a style in the cascade to a previous value, defined by a lower priority origin or layer. That includes a number of existing global CSS values, and a new revert-layer keyword that will also be global (works on any property).

Context: Existing global cascade keywords*

CSS has several global keywords which can be used on any property to help roll-back the cascade in various ways.

  • initial sets a property to the specified value before any styles (including browser defaults) are applied. This can be surprising as we often think of browser styles as the initial value — but, for example, the initial value of display is inline, no matter what element we use it on.
  • inherit sets the property to apply a value from its parent element. This is the default for inherited properties, but can still be used to remove a previous value.
  • unset acts as though simply removing all previous values — so that inherited properties once again inherit, while non-inherited properties return to their initial value.
  • revert only removes values that we’ve applied in the author origin (i.e. the site styles). This is what we want in most cases, since it allows the browser and user styles to remain intact.
New: the revert-layer keyword

Cascade layers add a new global revert-layer keyword. It works the same as revert, but only removes values that we’ve applied in the current cascade layer. We can use that to roll back the cascade, and use whatever value was defined in the previous layers.

In this example, the no-theme class removes any values set in the theme layer.

@layer default {
  a { color: maroon; }
}

@layer theme {
  a { color: var(--brand-primary, purple); }

  .no-theme {
    color: revert-layer;
  }
}

So a link tag with the .no-theme class will roll back to use the value set in the default layer. When revert-layer is used in un-layered styles, it behaves the same as revert — rolling back to the previous origin.

Reverting important layers

Things get interesting if we add !important to the revert-layer keyword. Because each layer has two distinct “normal” and “important” positions in the cascade, this doesn’t simply change the priority of the declaration — it changes what layers are reverted.

Let’s assume we have three layers defined, in a layer stack that looks like this:

  1. utilities (most powerful)
  2. components
  3. defaults (least powerful)

We can flesh that out to include not just normal and important positions of each layer, but also un-layered styles, and animations:

  1. !important defaults (most powerful)
  2. !important components
  3. !important utilities
  4. !important un-layered styles
  5. CSS animations
  6. normal un-layered styles
  7. normal utilities
  8. normal components
  9. normal defaults (least powerful)

Now, when we use revert-layer in a normal layer (let’s use utilities) the result is fairly direct. We revert only that layer, while everything else applies normally:

  1. !important defaults (most powerful)
  2. !important components
  3. !important utilities
  4. !important un-layered styles
  5. ✅ CSS animations
  6. ✅ normal un-layered styles
  7. ❌ normal utilities
  8. ✅ normal components
  9. ✅ normal defaults (least powerful)

But when we move that revert-layer into the important position, we revert both the normal and important versions along with everything in-between:

  1. !important defaults (most powerful)
  2. !important components
  3. !important utilities
  4. !important un-layered styles
  5. ❌ CSS animations
  6. ❌ normal un-layered styles
  7. ❌ normal utilities
  8. ✅ normal components
  9. ✅ normal defaults (least powerful)

Use cases: When would I want to use cascade layers?

So what sort of situations might we find ourselves using cascade layers? Here are several examples of when cascade layers make a lot of sense, as well as others where they do not make a lot sense.

Less intrusive resets and defaults

One of the clearest initial use cases would be to make low-priority defaults that are easy to override.

Some resets have been doing this already by applying the :where() pseudo-class around each selector. :where() removes all specificity from the selectors it is applied to, which has the basic impact desired, but also some downsides:

  • It has to be applied to each selector individually
  • Conflicts inside the reset have to be resolved without specificity

Layers allow us to more simply wrap the entire reset stylesheet, either using the block @layer rule:

/* reset.css */
@layer reset {
  /* all reset styles in here */
}

Or when you import the reset:

/* reset.css */
@import url(reset.css) layer(reset);

Or both! Layers can be nested without changing their priority. This way, you can use a third-party reset, and ensure it gets added to the layer you want whether or not the reset stylesheet itself is written using layers internally.

Since layered styles have a lower priority than default “un-layered” styles, this is a good way to start using cascade layers without re-writing your entire CSS codebase.

The reset selectors still have specificity information to help resolve internal conflicts, without wrapping each individual selector — but you also get the desired outcome of a reset stylesheet that is easy to override.

Managing a complex CSS architecture

As projects become larger and more complex, it can be useful to define clearer boundaries for naming and organizing CSS code. But the more CSS we have, the more potential we have for conflicts — especially from different parts of a system like a “theme” or a “component library” or a set of “utility classes.”

Not only do we want these organized by function, but it can also be useful to organize them based on what parts of the system take priority in the case of a conflict. Harry Robert’s Inverted Triangle CSS does a good job visualizing what those layers might contain.

In fact, the initial pitch for adding layers to the CSS cascade used the ITCSS methodology as a primary example, and a guide for developing the feature.

There is no particular technique required for this, but it’s likely helpful to restrict projects to a pre-defined set of top-level layers and then extend that set with nested layers as appropriate.

For example:

  1. low level reset and normalization styles
  2. element defaults, for basic typography and legibility
  3. themes, like light and dark modes
  4. re-usable patterns that might appear across multiple components
  5. layouts and larger page structures
  6. individual components
  7. overrides and utilities

We can create that top-level layer stack at the very start of our CSS, with a single layer statement:

@layer
  reset,
  default,
  themes,
  patterns,
  layouts,
  components,
  utilities;

The exact layers needed, and how you name those layers, might change from one project to the next.

From there, we create even more detailed layer breakdowns. Maybe our components themselves have defaults, structures, themes, and utilities internally.

@layer components {
  @layer defaults, structures, themes, utilities;
}

Without changing the top-level structure, we now have a way to further layer the styles within each component.

Using third-party tools and frameworks

Integrating third-party CSS with a project is one of the most common places to run into cascade issues. Whether we’re using a shared reset like Normalizer or CSS Remedy, a generic design system like Material Design, a framework like Bootstrap, or a utility toolkit like Tailwind — we can’t always control the selector specificity or importance of all the CSS being used on our sites. Sometimes, this even extends to internal libraries, design systems, and tools managed elsewhere in an organization.

As a result, we often have to structure our internal CSS around the third-party code, or escalate conflicts when they come up — with artificially high specificity or !important flags. And then we have to maintain those hacks over time, adapting to upstream changes.

Cascade layers give us a way to slot third-party code into the cascade of any project exactly where we want it to live — no matter how selectors are written internally. Depending on the type of library we’re using, we might do that in various ways. Let’s start with a basic layer-stack, working our way up from resets to utilities:

@layer reset, type, theme, components, utilities;

And then we can incorporate some tools…

Using a reset

If we’re using a tool like CSS Remedy, we might also have some reset styles of our own that we want to include. Let’s import CSS Remedy into a sub-layer of reset:

@import url('remedy.css') layer(reset.remedy);

Now we can add our own reset styles to the reset layer, without any further nesting (unless we want it). Since styles directly in reset will override any further nested styles, we can be sure our styles will always take priority over CSS Remedy if there’s a conflict — no matter what changes in a new release:

@import url('remedy.css') layer(reset.remedy);

@layer reset {
  :is(ol, ul)[role='list'] {
    list-style: none;
    padding-inline-start: 0;
  }
}

And since the reset layer is at the bottom of the stack, the rest of the CSS in our system will override both Remedy, and our own local reset additions.

Using utility classes

At the other end of our stack, “utility classes” in CSS can be a useful way to reproduce common patterns (like additional context for screen readers) in a broadly-applicable way. Utilities tend to break the specificity heuristic, since we want them defined broadly (resulting in a low specificity), but we also generally want them to “win” conflicts.

By having a utilities layer at the top of our layer stack, we can make that possible. We can use that in a similar way to the reset example, both loading external utilities into a sub-layer, and providing our own:

@import url('tailwind.css') layer(utilities.tailwind);

@layer utilities {
  /* from https://kittygiraudel.com/snippets/sr-only-class/ */
  /* but with !important removed from the properties */
  .sr-only {
    border: 0;
    clip: rect(1px, 1px, 1px, 1px);
    -webkit-clip-path: inset(50%);
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    margin: -1px;
    padding: 0;
    position: absolute;
    width: 1px;
    white-space: nowrap;
  }
}
Using design systems and component libraries

There are a lot of CSS tools that fall somewhere in the middle of our layer stack — combining typography defaults, themes, components, and other aspects of a system.

Depending on the particular tool, we might do something similar to the reset and utility examples above — but there are a few other options. A highly integrated tool might deserve a top-level layer:

@layer reset, bootstrap, utilities;
@import url('bootstrap.css') layer(bootstrap);

If these tools start to provide layers as part of their public API, we could also break it down into parts — allowing us to intersperse our code with the library:

@import url('bootstrap/reset.css') layer(reset.bootstrap);
@import url('bootstrap/theme.css') layer(theme.bootstrap);
@import url('bootstrap/components.css') layer(components.bootstrap);

@layer theme.local {
  /* styles here will override theme.bootstrap */
  /* but not interfere with styles from components.bootstrap */
}

Using layers with existing (un-layered, !important-filled) frameworks

As with any major language change, there’s going to be an adjustment period when CSS Cascade Layers become widely adopted. What happens if your team is ready to start using layers next month, but your favorite framework decides to wait another three years before they switch over to layered styles? Many frameworks will likely still use !important more often than we’d like! With !important layers reversed, that’s not ideal.

Still, layers can still help us solve the problem. We just have to get clever about it. We decide what layers we want for our project, and that means we can add layers above and also below the framework layers we create.

For now, though, we can use a lower layer to override !important styles from the framework, and a higher layer to override normal styles. Something like this:

@layer framework.important, framework.bootstrap, framework.local;
@import url('bootstrap.css') layer(framework.bootstrap);

@layer framework.local {
  /* most of our normal framework overrides can live here */
}

@layer framework.important {
  /* add !important styles in a lower layer */
  /* to override any !important framework styles */
}

It still feels like a bit of a hack, but it helps move us in the right direction — towards a more structured cascade. Hopefully it’s a temporary fix.

Designing a CSS tool or framework

For anyone maintaining a CSS library, cascade layers can help with internal organization, and even become part of the developer API. By naming internal layers of a library, we can allow users of our framework to hook into those layers when customizing or overriding our provided styles.

For example, Bootstrap could expose layers for their “reboot,” “grid,” and “utilities” — likely stacked in that order. Now a user can decide if they want to load those Bootstrap layers into different local layers:

@import url(bootstrap/reboot.css) layer(reset); /* reboot » reset.reboot */
@import url(bootstrap/grid.css) layer(layout); /* grid » layout.grid */
@import url(bootstrap/utils.css) layer(override); /* utils » override.utils */

Or the user might load them into a Bootstrap layer, with local layers interspersed:

@layer bs.reboot, bs.grid, bs.grid-overrides, bs.utils, bs.util-overrides;
@import url('bootstrap-all.css') layer(bs);

It’s also possible to hide internal layering from users, when desired, by grouping any private/internal layers inside an anonymous (un-named) layer. Anonymous layers will get added to the layer order where they are encountered, but will not be exposed to users re-arranging or appending styles.

I just want this one property to be more !important

Counter to some expectations, layers don’t make it easy to quickly escalate a particular style so that it overrides another.

If the majority of our styles are un-layered, then any new layer will be de-prioritized in relation to the default. We could do that to individual style blocks, but it would quickly become difficult to track.

Layers are intended to be more foundational, not style-by-style, but establishing consistent patterns across a project. Ideally, if we’ve set that up right, we get the correct result by moving our style to the appropriate (and pre-defined) layer.

If the majority of our styles already fall into well-defined layers, we can always consider adding a new highest-power layer at the top of a given stack, or using un-layered styles to override the layers. We might even consider having a debug layer at the top of the stack, for doing exploratory work outside of production.

But adding new layers on-the-fly can defeat the organizational utility of this feature, and should be used carefully. It’s best to ask: Why should this style override the other?

If the answer has to do with one type of style always overriding another type, layers are probably the right solution. That might be because we’re overriding styles that come from a place we don’t control, or because we’re writing a utility, and it should move into our utilities layer. If the answer has to do with more targeted styles overriding less targeted styles, we might consider making the selectors reflect that specificity.

Or, on rare occasions, we might even have styles that really are important — the feature simply doesn’t work if you override this particular style. We might say adding display: none to the [hidden] attribute belongs in our lowest-priority reset, but should still be hard to override. In that case, !important really is the right tool for the job:

@layer reset {
  [hidden] { display: none !important; }
}

Scoping and name-spacing styles? Nope!

Cascade layers are clearly an organizational tool, and one that ‘captures’ the impact of selectors, especially when they conflict. So it can be tempting at first glance to see them as a solution for managing scope or name-spacing.

A common first-instinct is to create a layer for each component in a project — hoping that will ensure (for example) that .post-title is only applied inside a .post.

But cascade conflicts are not the same as naming conflicts, and layers aren’t particularly well designed for this type of scoped organization. Cascade layers don’t constrain how selectors match or apply to the HTML, only how they cascade together. So unless we can be sure that component X always override component Y, individual component layers won’t help much. Instead, we’ll need to keep an eye on the proposed @scope spec that is being developed.

It can be useful to think of layers and component-scopes instead as overlapping concerns:

An illustration showing how CSS Cascade Layers can be organized by scope, such as buttons, cards, and login layers that fall into component, theme, and default scopes.

Scopes describe what we are styling, while layers describe why we are styling. We can also think of layers as representing where the style comes from, while scopes represent what the style will attach to.

Test your knowledge: Which style wins?

For each situation, assume this paragraph:

<p id="intro">Hello, World!</p>

Question 1

@layer ultra-high-priority {
  #intro {
    color: red;
  }
}

p {
  color: green;
}
What color is the paragraph?

Despite the layer having a name that sounds pretty important, un-layered styles have a higher priority in the cascade. So the paragraph will be green.

Question 2

@layer ren, stimpy;

@layer ren {
  p { color: red !important; }
}

p { color: green; }

@layer stimpy {
  p { color: blue !important; }
}
What color is the paragraph?

Our normal layer order is established at the start — ren at the bottom, then stimpy, then (as always) un-layered styles at the top. But these styles aren’t all normal, some of them are important. Right away, we can filter down to just the !important styles, and ignore the unimportant green. Remember that ‘origins and importance’ are the first step of the cascade, before we even take layering into account.

That leaves us with two important styles, both in layers. Since our important layers are reversed, ren moves to the top, and stimpy to the bottom. The paragraph will be red.

Question 3

@layer Montagues, Capulets, Verona;

@layer Montagues.Romeo { #intro { color: red; } }
@layer Montagues.Benvolio { p { color: orange; } }

@layer Capulets.Juliet { p { color: yellow; } }
@layer Verona { * { color: blue; } }
@layer Capulets.Tybalt { #intro { color: green; } }
What color is the paragraph?

All our styles are in the same origin and context, none are marked as important, and none of them are inline styles. We do have a broad range of selectors here, from a highly specific ID #intro to a zero specificity universal * selector. But layers are resolved before we take specificity into account, so we can ignore the selectors for now.

The primary layer order is established up front, and then sub-layers are added internally. But sub-layers are sorted along with their parent layer — meaning all the Montagues will have lowest priority, followed by all the Capulets, and then Verona has final say in the layer order. So we can immediately filter down to just the Verona styles, which take precedence. Even though the * selector has zero specificity, it will win.

Be careful about putting universal selectors in powerful layers!

Debugging layer conflicts in browser developer tools

Chrome, Safari, Firefox, and Edge browsers all have developer tools that allow you to inspect the styles being applied to a given element on the page. The styles panel of this element inspector will show applied selectors, sorted by their cascade priority (highest priority at the top), and then inherited styles below. Styles that are not being applied for any reason will generally be grayed out, or even crossed out — sometimes with additional information about why the style is not applied. This is the first place to look when debugging any aspect of the cascade, including layer conflicts.

Safari Technology Preview and Firefox Nightly already show (and sort) cascade layers in this panel. This tooling is expected to role out in the stable versions at the same time as cascade layers. The layer of each selector is listed directly above the selector itself:

Showing CSS Cascade Layers in Safari DevTools.
Safari
Showing CSS Cascade Layers in FireFox DevTools.
Firefox

Chrome/Edge are working on similar tools and expect to have them available in Canary (nightly) releases by the time cascade layers land in the stable release. We’ll make updates here as those tools change and improve.

Browser support and fallbacks

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
9997No9915.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712715.4

Since layers are intended as foundational building blocks of an entire CSS architecture, it is difficult to imagine building manual fallbacks in the same way you might for other CSS features. The fallbacks would likely involve duplicating large sections of code, with different selectors to manage cascade layering — or providing a much simpler fallback stylesheet.

Query feature support using @supports

There is a @supports feature in CSS that will allow authors to test for support of @layer and other at-rules:

@supports at-rule(@layer) {
  /* code applied for browsers with layer support */
}

@supports not at-rule(@layer) {
  /* fallback applied for browsers without layer support */
}

However, it’s also not clear when this query itself will be supported in browsers.

There is no official specification yet for a syntax to layer entire stylesheets from the html <link> tag, but there is a proposal being developed. That proposal includes a new layer attribute which can be used to assign the styles to a named or anonymous layer:

<!-- styles imported into to the <layer-name> layer -->
<link rel="stylesheet" href="example.css" layer="<layer-name>">

<!-- styles imported into to a new anonymous layer -->
<link rel="stylesheet" href="example.css" layer>

However, old browsers without support for the layer attribute will ignore it completely, and continue to load the stylesheet without any layering. The results could be pretty unexpected. So the proposal also extends the existing media attribute, so that it allows feature support queries in a support() function.

That would allow us to make layered links conditional, based on support for layering:

<link rel="stylesheet" layer="bootstrap" media="supports(at-rule(@layer))" href="bootstrap.css">

Potential polyfills and workarounds

The major browsers have all moved to an “evergreen” model with updates pushed to users on a fairly short release cycle. Even Safari regularly releases new features in “patch” updates between their more rare-seeming major versions.

That means we can expect browser support for these features to ramp up very quickly. For many of us, it may be reasonable to start using layers in only a few months, without much concern for old browsers.

For others, it may take longer to feel comfortable with the native browser support. There are many other ways to manage the cascade, using selectors, custom properties, and other tools. It’s also theoretically possible to mimic (or polyfill) the basic behavior. There are people working on that polyfill, but it’s not clear when that will be ready either.

More resources

CSS Cascade Layers is still evolving but there is already a lot of resources, including documentation, articles, videos, and demos to help you get even more familiar with layers and how they work.

Reference

Articles

Videos

Demos


A Complete Guide to CSS Cascade Layers originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-cascade-layers/feed/ 27 363766
Very Extremely Practical CSS Art https://css-tricks.com/very-extremely-practical-css-art/ https://css-tricks.com/very-extremely-practical-css-art/#comments Wed, 09 Dec 2020 00:36:06 +0000 https://css-tricks.com/?p=330337 I’ve always enjoyed the CSS art people create, but I’ve never ventured into it much myself. I’m familiar with many of the tricks involved, but still find it surprising every time: the way people are able to make such fluid …


Very Extremely Practical CSS Art originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’ve always enjoyed the CSS art people create, but I’ve never ventured into it much myself. I’m familiar with many of the tricks involved, but still find it surprising every time: the way people are able to make such fluid and beautiful images out of little boxes. I always end up digging around in dev tools to see how things are done, but I had never seen the process unfold.

Any time CSS art starts getting attention, there is always someone around to say “that’s not practical” or “just use SVG” or something similarly dismissive and boring. It’s a terrible argument, even if it was true — no one is required to be Practical At All Times. What a terrible world that would be.

In October, I took the time to watch Lynn Fisher (Twitter, CodePen), one of my favorite CSS artists, live-stream her single-div process. Somewhere in the back of my mind, I assumed single-div artwork relied on highly complicated box-shadows—almost a pixel-art approach. I’m not sure where that idea came from, I probably saw someone do it years ago. But her process is much more “normal” and “practical” than I even realized: a number of reasonably layered, sized, and positioned background gradients.

Examples of Lynn Fischer's single div projects: repeated polar bears, plants on a shelf, a blinking light, and a tiny electronic piano.

Wait. I know how to do that. It’s not the technique that’s magical—it’s the audacity of turning a few gradients into a block of cheese with cake inside!

I’ve used all these properties before on client projects. I’ve created gradients, layered images, sized them, and positioned them for various effects. None of that is new, or complicated, or radical. I really didn’t learn anything at all about the CSS itself. But it had a huge impact on my perception of what I could accomplish with those simple tools. 

Within a few weeks, I was using that in production. Again, it’s nothing fancy or complicated—the perfect low-hanging fruit where a custom SVG feels just slightly too bulky. Here’s the effect I created, for a personal project, with a few custom properties to make adjustment easier:

Last week we used a similar trick as part of a Very Practical & Official client component library. It was Stacy Kvernmo’s idea, and it worked great.

Thanks Lynn, and all you other fabulous CSS Artists! Thanks for showing us all how much farther we can push this language that we love so much, and the Very Serious tools we use every day.


Very Extremely Practical CSS Art originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/very-extremely-practical-css-art/feed/ 2 330337
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
Embracing the Universal Web https://css-tricks.com/embracing-the-universal-web/ https://css-tricks.com/embracing-the-universal-web/#comments Thu, 21 Nov 2019 14:52:55 +0000 https://css-tricks.com/?p=298276 There are constantly new features appearing in browsers—from subgrid to variable fonts to better developer tools. It’s a really great time to be re-thinking everything we know about design on the web. Responsive design has served us well over the …


Embracing the Universal Web originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There are constantly new features appearing in browsers—from subgrid to variable fonts to better developer tools. It’s a really great time to be re-thinking everything we know about design on the web. Responsive design has served us well over the years, but it’s still rooted in the limitations of the web from 2010. Ten years later, we’re able to create more “Intrinsic” designs (a term coined by Jen Simmons) and hopefully, re-think some “best practices” that are now holding us back.

That’s exciting but even more interesting to me in the context of a universal web. I began my career during the height of the Web Standards Project, CSS Zen Garden, Designing with Web Standards, and A Dao of Web Design—saturated in the philosophy of universally accessible design. CSS was relatively new, and explicitly designed to balance the desires of a web-creator (like me) with the needs of each individual user (also me), on any number of devices. Terms like “progressive enhancement” and “unobtrusive JavaScript” dominated the industry. We were moving away from “only works in X browser” warnings, to embrace an accessible and resilient user-centered medium.

Over the years, some of those concerns have become industry best-practice. CSS is now standard, and responsive design is the norm. But I also see (and build!) more and more apps targeted at a narrow range of modern, evergreen browsers. Or we ignore new features for years in order to support a browser like IE11. We’ve become resigned to a sense that browser support is binary, and we can only use the features that exactly match our “supported” browsers.

There are many reasons for that shift, including excitement around very cool new features. I don’t think it’s surprising for an industry to have these cycles, but I do think it’s time to reflect on where we’re heading. These new features are designed with universal accessibility in mind, and we also have new features for managing browser-support on a continuum, much like viewport-size.

Whatever we want to call it—Intrinsic Design, Resilient CSS, Progressive Enhancement, Universal Accessibility, or something else—I think we’re poised for a new movement and a new era of web creation. It’s time for us to take the lessons we learned from Responsive Web Design, adapting to screen sizes, and expand out: adapting to screen readers, legacy browsers, “smart” speakers, and any number of other interfaces.

I’m interested in new methodologies and conventions that move past managing specificity and cascade, or phones and laptops, to help us all manage accessibility and universal compatibility. I’m interested in finding ways to embrace all that is wonderful and new on the web, without abandoning the beautiful vision of a universal web. We have the tools. Let’s do this.


Embracing the Universal Web originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/embracing-the-universal-web/feed/ 2 298276
Introducing Sass Modules https://css-tricks.com/introducing-sass-modules/ https://css-tricks.com/introducing-sass-modules/#comments Mon, 07 Oct 2019 14:24:56 +0000 https://css-tricks.com/?p=296858 Sass just launched a major new feature you might recognize from other languages: a module system. This is a big step forward for @import. one of the most-used Sass-features. While the current @import rule allows you to pull …


Introducing Sass Modules originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Sass just launched a major new feature you might recognize from other languages: a module system. This is a big step forward for @import. one of the most-used Sass-features. While the current @import rule allows you to pull in third-party packages, and split your Sass into manageable “partials,” it has a few limitations:

  • @import is also a CSS feature, and the differences can be confusing
  • If you @import the same file multiple times, it can slow down compilation, cause override conflicts, and generate duplicate output.
  • Everything is in the global namespace, including third-party packages – so my color() function might override your existing color() function, or vice versa.
  • When you use a function like color(). it’s impossible to know exactly where it was defined. Which @import does it come from?

Sass package authors (like me) have tried to work around the namespace issues by manually prefixing our variables and functions — but Sass modules are a much more powerful solution. In brief, @import is being replaced with more explicit @use and @forward rules. Over the next few years Sass @import will be deprecated, and then removed. You can still use CSS imports, but they won’t be compiled by Sass. Don’t worry, there’s a migration tool to help you upgrade!

Import files with @use

@use 'buttons';

The new @use is similar to @import. but has some notable differences:

  • The file is only imported once, no matter how many times you @use it in a project.
  • Variables, mixins, and functions (what Sass calls “members”) that start with an underscore (_) or hyphen (-) are considered private, and not imported.
  • Members from the used file (buttons.scss in this case) are only made available locally, but not passed along to future imports.
  • Similarly, @extends will only apply up the chain; extending selectors in imported files, but not extending files that import this one.
  • All imported members are namespaced by default.

When we @use a file, Sass automatically generates a namespace based on the file name:

@use 'buttons'; // creates a `buttons` namespace
@use 'forms'; // creates a `forms` namespace

We now have access to members from both buttons.scss and forms.scss — but that access is not transferred between the imports: forms.scss still has no access to the variables defined in buttons.scss. Because the imported features are namespaced, we have to use a new period-divided syntax to access them:

// variables: <namespace>.$variable
$btn-color: buttons.$color;
$form-border: forms.$input-border;

// functions: <namespace>.function()
$btn-background: buttons.background();
$form-border: forms.border();

// mixins: @include <namespace>.mixin()
@include buttons.submit();
@include forms.input();

We can change or remove the default namespace by adding as <name> to the import:

@use 'buttons' as *; // the star removes any namespace
@use 'forms' as f;

$btn-color: $color; // buttons.$color without a namespace
$form-border: f.$input-border; // forms.$input-border with a custom namespace

Using as * adds a module to the root namespace, so no prefix is required, but those members are still locally scoped to the current document.

Import built-in Sass modules

Internal Sass features have also moved into the module system, so we have complete control over the global namespace. There are several built-in modules — math, color, string, list, map, selector, and meta — which have to be imported explicitly in a file before they are used:

@use 'sass:math';
$half: math.percentage(1/2);

Sass modules can also be imported to the global namespace:

@use 'sass:math' as *;
$half: percentage(1/2);

Internal functions that already had prefixed names, like map-get or str-index. can be used without duplicating that prefix:

@use 'sass:map';
@use 'sass:string';
$map-get: map.get(('key': 'value'), 'key');
$str-index: string.index('string', 'i');

You can find a full list of built-in modules, functions, and name changes in the Sass module specification.

New and changed core features

As a side benefit, this means that Sass can safely add new internal mixins and functions without causing name conflicts. The most exciting example in this release is a sass:meta mixin called load-css(). This works similar to @use but it only returns generated CSS output, and it can be used dynamically anywhere in our code:

@use 'sass:meta';
$theme-name: 'dark';

[data-theme='#{$theme-name}'] {
  @include meta.load-css($theme-name);
}

The first argument is a module URL (like @use) but it can be dynamically changed by variables, and even include interpolation, like theme-#{$name}. The second (optional) argument accepts a map of configuration values:

// Configure the $base-color variable in 'theme/dark' before loading
@include meta.load-css(
  'theme/dark', 
  $with: ('base-color': rebeccapurple)
);

The $with argument accepts configuration keys and values for any variable in the loaded module, if it is both:

  • A global variable that doesn’t start with _ or - (now used to signify privacy)
  • Marked as a !default value, to be configured
// theme/_dark.scss
$base-color: black !default; // available for configuration
$_private: true !default; // not available because private
$config: false; // not available because not marked as a !default

Note that the 'base-color' key will set the $base-color variable.

There are two more sass:meta functions that are new: module-variables() and module-functions(). Each returns a map of member names and values from an already-imported module. These accept a single argument matching the module namespace:

@use 'forms';

$form-vars: module-variables('forms');
// (
//   button-color: blue,
//   input-border: thin,
// )

$form-functions: module-functions('forms');
// (
//   background: get-function('background'),
//   border: get-function('border'),
// )

Several other sass:meta functions — global-variable-exists(), function-exists(), mixin-exists(), and get-function() — will get additional $module arguments, allowing us to inspect each namespace explicitly.

Adjusting and scaling colors

The sass:color module also has some interesting caveats, as we try to move away from some legacy issues. Many of the legacy shortcuts like lighten(). or adjust-hue() are deprecated for now in favor of explicit color.adjust() and color.scale() functions:

// previously lighten(red, 20%)
$light-red: color.adjust(red, $lightness: 20%);

// previously adjust-hue(red, 180deg)
$complement: color.adjust(red, $hue: 180deg);

Some of those old functions (like adjust-hue) are redundant and unnecessary. Others — like lighten. darken. saturate. and so on — need to be re-built with better internal logic. The original functions were based on adjust(). which uses linear math: adding 20% to the current lightness of red in our example above. In most cases, we actually want to scale() the lightness by a percentage, relative to the current value:

// 20% of the distance to white, rather than current-lightness + 20
$light-red: color.scale(red, $lightness: 20%);

Once fully deprecated and removed, these shortcut functions will eventually re-appear in sass:color with new behavior based on color.scale() rather than color.adjust(). This is happening in stages to avoid sudden backwards-breaking changes. In the meantime, I recommend manually checking your code to see where color.scale() might work better for you.

Configure imported libraries

Third-party or re-usable libraries will often come with default global configuration variables for you to override. We used to do that with variables before an import:

// _buttons.scss
$color: blue !default;

// old.scss
$color: red;
@import 'buttons';

Since used modules no longer have access to local variables, we need a new way to set those defaults. We can do that by adding a configuration map to @use:

@use 'buttons' with (
  $color: red,
  $style: 'flat',
);

This is similar to the $with argument in load-css(). but rather than using variable-names as keys, we use the variable itself, starting with $.

I love how explicit this makes configuration, but there’s one rule that has tripped me up several times: a module can only be configured once, the first time it is used. Import order has always been important for Sass, even with @import. but those issues always failed silently. Now we get an explicit error, which is both good and sometimes surprising. Make sure to @use and configure libraries first thing in any “entrypoint” file (the central document that imports all partials), so that those configurations compile before other @use of the libraries.

It’s (currently) impossible to “chain” configurations together while keeping them editable, but you can wrap a configured module along with extensions, and pass that along as a new module.

Pass along files with @forward

We don’t always need to use a file, and access its members. Sometimes we just want to pass it along to future imports. Let’s say we have multiple form-related partials, and we want to import all of them together as one namespace. We can do that with @forward:

// forms/_index.scss
@forward 'input';
@forward 'textarea';
@forward 'select';
@forward 'buttons';

Members of the forwarded files are not available in the current document and no namespace is created, but those variables, functions, and mixins will be available when another file wants to @use or @forward the entire collection. If the forwarded partials contain actual CSS, that will also be passed along without generating output until the package is used. At that point it will all be treated as a single module with a single namespace:

// styles.scss
@use 'forms'; // imports all of the forwarded members in the `forms` namespace

Note: if you ask Sass to import a directory, it will look for a file named index or _index)

By default, all public members will forward with a module. But we can be more selective by adding show or hide clauses, and naming specific members to include or exclude:

// forward only the 'input' border() mixin, and $border-color variable
@forward 'input' show border, $border-color;

// forward all 'buttons' members *except* the gradient() function
@forward 'buttons' hide gradient;

Note: when functions and mixins share a name, they are shown and hidden together.

In order to clarify source, or avoid naming conflicts between forwarded modules, we can use as to prefix members of a partial as we forward:

// forms/_index.scss
// @forward "<url>" as <prefix>-*;
// assume both modules include a background() mixin
@forward 'input' as input-*;
@forward 'buttons' as btn-*;

// style.scss
@use 'forms';
@include forms.input-background();
@include forms.btn-background();

And, if we need, we can always @use and @forward the same module by adding both rules:

@forward 'forms';
@use 'forms';

That’s particularly useful if you want to wrap a library with configuration or any additional tools, before passing it along to your other files. It can even help simplify import paths:

// _tools.scss
// only use the library once, with configuration
@use 'accoutrement/sass/tools' with (
  $font-path: '../fonts/',
);
// forward the configured library with this partial
@forward 'accoutrement/sass/tools';

// add any extensions here...


// _anywhere-else.scss
// import the wrapped-and-extended library, already configured
@use 'tools';

Both @use and @forward must be declared at the root of the document (not nested), and at the start of the file. Only @charset and simple variable definitions can appear before the import commands.

Moving to modules

In order to test the new syntax, I built a new open source Sass library (Cascading Color Systems) and a new website for my band — both still under construction. I wanted to understand modules as both a library and website author. Let’s start with the “end user” experience of writing site styles with the module syntax…

Maintaining and writing styles

Using modules on the website was a pleasure. The new syntax encourages a code architecture that I already use. All my global configuration and tool imports live in a single directory (I call it config), with an index file that forwards everything I need:

// config/_index.scss
@forward 'tools';
@forward 'fonts';
@forward 'scale';
@forward 'colors';

As I build out other aspects of the site, I can import those tools and configurations wherever I need them:

// layout/_banner.scss
@use '../config';

.page-title {
  @include config.font-family('header');
}

This even works with my existing Sass libraries, like Accoutrement and Herman, that still use the old @import syntax. Since the @import rule will not be replaced everywhere overnight, Sass has built in a transition period. Modules are available now, but @import will not be deprecated for another year or two — and only removed from the language a year after that. In the meantime, the two systems will work together in either direction:

  • If we @import a file that contains the new @use/@forward syntax, only the public members are imported, without namespace.
  • If we @use or @forward a file that contains legacy @import syntax, we get access to all the nested imports as a single namespace.

That means you can start using the new module syntax right away, without waiting for a new release of your favorite libraries: and I can take some time to update all my libraries!

Migration tool

Upgrading shouldn’t take long if we use the Migration Tool built by Jennifer Thakar. It can be installed with Node, Chocolatey, or Homebrew:

npm install -g sass-migrator
choco install sass-migrator
brew install sass/sass/migrator

This is not a single-use tool for migrating to modules. Now that Sass is back in active development (see below), the migration tool will also get regular updates to help migrate each new feature. It’s a good idea to install this globally, and keep it around for future use.

The migrator can be run from the command line, and will hopefully be added to third-party applications like CodeKit and Scout as well. Point it at a single Sass file, like style.scss. and tell it what migration(s) to apply. At this point there’s only one migration called module:

# sass-migrator <migration> <entrypoint.scss...>
sass-migrator module style.scss

By default, the migrator will only update a single file, but in most cases we’ll want to update the main file and all its dependencies: any partials that are imported, forwarded, or used. We can do that by mentioning each file individually, or by adding the --migrate-deps flag:

sass-migrator --migrate-deps module style.scss

For a test-run, we can add --dry-run --verbose (or -nv for short), and see the results without changing any files. There are a number of other options that we can use to customize the migration — even one specifically for helping library authors remove old manual namespaces — but I won’t cover all of them here. The migration tool is fully documented on the Sass website.

Updating published libraries

I ran into a few issues on the library side, specifically trying to make user-configurations available across multiple files, and working around the missing chained-configurations. The ordering errors can be difficult to debug, but the results are worth the effort, and I think we’ll see some additional patches coming soon. I still have to experiment with the migration tool on complex packages, and possibly write a follow-up post for library authors.

The important thing to know right now is that Sass has us covered during the transition period. Not only can imports and modules work together, but we can create “import-only” files to provide a better experience for legacy users still @importing our libraries. In most cases, this will be an alternative version of the main package file, and you’ll want them side-by-side: <name>.scss for module users, and <name>.import.scss for legacy users. Any time a user calls @import <name>, it will load the .import version of the file:

// load _forms.scss
@use 'forms';

// load _forms.input.scss
@import 'forms';

This is particularly useful for adding prefixes for non-module users:

// _forms.import.scss
// Forward the main module, while adding a prefix
@forward "forms" as forms-*;

Upgrading Sass

You may remember that Sass had a feature-freeze a few years back, to get various implementations (LibSass, Node Sass, Dart Sass) all caught up, and eventually retired the original Ruby implementation. That freeze ended last year, with several new features and active discussions and development on GitHub – but not much fanfare. If you missed those releases, you can get caught up on the Sass Blog:

Dart Sass is now the canonical implementation, and will generally be the first to implement new features. If you want the latest, I recommend making the switch. You can install Dart Sass with Node, Chocolatey, or Homebrew. It also works great with existing gulp-sass build steps.

Much like CSS (since CSS3), there is no longer a single unified version-number for new releases. All Sass implementations are working from the same specification, but each one has a unique release schedule and numbering, reflected with support information in the beautiful new documentation designed by Jina.

Sass Modules are available as of October 1st, 2019 in Dart Sass 1.23.0.


Introducing Sass Modules originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/introducing-sass-modules/feed/ 17 296858
More CSS Charts, with Grid & Custom Properties https://css-tricks.com/css-charts-grid-custom-properties/ https://css-tricks.com/css-charts-grid-custom-properties/#comments Fri, 11 Aug 2017 17:54:45 +0000 http://css-tricks.com/?p=257419 I loved Robin’s recent post, experimenting with CSS Grid for bar-charts. I’ve actually been using a similar approach on a client project, building a day-planner with CSS Grid. It’s a different use-case, but the same basic technique: using grid …


More CSS Charts, with Grid & Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I loved Robin’s recent post, experimenting with CSS Grid for bar-charts. I’ve actually been using a similar approach on a client project, building a day-planner with CSS Grid. It’s a different use-case, but the same basic technique: using grid layouts to visualize data.

(I recommend reading Robin’s article first, since I’m building on top of his chart.)

Robin’s approach relies on a large Sass loop to generate 100 potential class-names, even though less than 12 are used in the final chart. In production we’ll want something more direct and performant, with better semantics, so I turned to definition lists and CSS Variables (aka Custom Properties) to build my charts.

Here’s the final result:

See the Pen Bar chart in CSS grid + variables by Miriam Suzanne (@mirisuzanne) on CodePen.

Let’s dig into it!

Markup First

Robin was proposing a conceptual experiment, so he left out many real-life data and accessibility concerns. Since I’m aiming for (fictional) production code, I want to make sure it will be semantic and accessible. I borrowed the year-axis idea from a comment on Robin’s charts, and moved everything into a definition list. Each year is associated with a corresponding percentage in the list:

<dl class="chart">
  <dt class="date">2000</dt>
  <dd class="bar">45%</dd>

  <dt class="date">2001</dt> 
  <dd class="bar">100%</dd>
  
  <!-- etc… -->
</dl>

There are likely other ways to mark this up accessibly, but a dl seemed clean and clear to me – with all the data and associated pairs available as structured text. By default, this displays year/percentage pairs in a readable format. Now we have to make it beautiful.

Grid Setup

I started from Robin’s grid, but my markup requires an extra row for the .date elements. I add that to the end of my grid-template-rows, and place my date/bar elements appropriately:

.chart {
  display: grid;
  grid-auto-columns: 1fr;
  grid-template-rows: repeat(100, 1fr) 1.4rem;
  grid-column-gap: 5px;
}

.date {
  /* fill the bottom row */
  grid-row-start: -2;
}

.bar {
  /* end before the bottom row */
  grid-row-end: -2;
}

Normally, I would use auto for that final row, but I needed an explicit height to make the background-grid work properly. Not not worth the trade-off, probably, but I was having fun.

Passing Data to CSS

At this point, CSS has no access to the relevant numbers for styling a chart. We have no hooks for setting individual bars to different heights. Robin’s solution involves individual class-names for every bar-value, with a Sass to loop to create custom classes for each value. That works, but we end up with a long list of classes we may never use. Is there a way to pass data into CSS more directly?

The most direct approach might be an inline style:

<dd class="bar" style="grid-row-start: 56">45%</dd>

The start position is the full number of grid lines (one more than the number of rows, or 101 in this case), minus the total value of the given bar: 101 - 45 = 56. That works fine, but now our markup and CSS are tightly coupled. With CSS Variables, we can pass in raw data, and let the CSS decide how it is used:

<dd class="bar" style="--start: 56">45%</dd>

In the CSS we can wire that up to grid-row-start:

.bar {
  grid-row-start: var(--start);
}

We’ve replaced the class-name loop, and bloated 100-class output, with a single line of dynamic CSS. Variables also remove the danger normally associated with CSS-in-HTML. While an inline property like grid-row-start will be nearly impossible to override from a CSS file, the inline variable can simply be ignored by CSS. There are no specificity/cascade issues to worry about.

Data-Driven Backgrounds

As a bonus, we can do more with the data than simply provide a grid-position – reusing it to style a fallback option, or even adjust the bar colors based on that same data:

.bar {
  background-image: linear-gradient(to right, green, yellow, orange, red);
  background-size: 1600% 100%;
  
  /* turn the start value into a percentage for position on the gradient */
  background-position: calc(var(--start) * 1%) 0;
}

I started with a horizontal background gradient from green to yellow, orange, and then red. Then I used background-size to make the gradient much wider than the bar – at least 200% per color (800%). Larger gradient-widths will make the fade less visible, so I went with 1600% to keep it subtle. Finally, using calc() to convert our start position (1-100) into a percentage, I can adjust the background position left-or-right based on the value – showing a different color depending on the percentage.

The background grid is also generated using variables and background-gradients. Sadly, subpixel rounding makes it a bit unreliable, but you can play with the --line-every value to change the level of detail. Take a look around, and see what other improvements you can make!

Adding Scale [without Firefox]

Right now, we’re passing in a start position rather than a pure value (“56” for “45%”). That start position is based on an assumption that the overall scale is 100%. In order to make this a more flexible tool, I thought it would be fun to contain all the math, including the scale, inside CSS. Here’s what it would look like:

<dl class="chart" style="--scale: 100">
  <dt class="date">2000</dt>
  <dd class="bar" style="--value: 45">45%</dd>

  <dt class="date">2001</dt> 
  <dd class="bar" style="--value: 100">100%</dd>
  
  <!-- etc… -->
</dl>

Then we can calculate the --start value in CSS, before applying it.

.bar {
  --start: calc(var(--scale) + 1 - var(--value));
  grid-row-start: var(--start);
}

With both the overall scale and individual values in CSS, we can manipulate either one individually. Change the scale to 200%, and watch the chart update accordingly:

See the Pen Bar Chart with Sale – no firefox by Miriam Suzanne (@mirisuzanne) on CodePen.

Both Chrome and Safari handle it beautifully, but Firefox seems unhappy about calc values in grid-positioning. I imagine they’ll get it fixed eventually. For now, we’ll just have to leave some calculations out of our CSS.

Sad, but we’ll get used to it. 😉

There is much more we could do, providing fallbacks for older browsers – but I do think this is a viable option with potential to be accessible, semantic, performant, and beautiful. Thanks for starting that conversation, Robin!


More CSS Charts, with Grid & Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-charts-grid-custom-properties/feed/ 7 257419
Fun with Viewport Units https://css-tricks.com/fun-viewport-units/ https://css-tricks.com/fun-viewport-units/#comments Mon, 05 Jun 2017 12:50:11 +0000 http://css-tricks.com/?p=252583 Viewport units have been around for several years now, with near-perfect support in the major browsers, but I keep finding new and exciting ways to use them. I thought it would be fun to review the basics, and then …


Fun with Viewport Units originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Viewport units have been around for several years now, with near-perfect support in the major browsers, but I keep finding new and exciting ways to use them. I thought it would be fun to review the basics, and then round-up some of my favorite use-cases.

What are viewport units?

Four new “viewport-relative” units appeared in the CSS specifications between 2011 and 2015, as part of the W3C’s CSS Values and Units Module Level 3. The new units – vw, vh, vmin, and vmax – work similarly to existing length units like px or em, but represent a percentage of the current browser viewport.

  • Viewport Width (vw) – A percentage of the full viewport width. 10vw will resolve to 10% of the current viewport width, or 48px on a phone that is 480px wide. The difference between % and vw is most similar to the difference between em and rem. A % length is relative to local context (containing element) width, while a vw length is relative to the full width of the browser window.
  • Viewport Height (vh) – A percentage of the full viewport height. 10vh will resolve to 10% of the current viewport height.
  • Viewport Minimum (vmin) – A percentage of the viewport width or height, whichever is smaller10vmin will resolve to 10% of the current viewport width in portrait orientations, and 10% of the viewport height on landscape orientations.
  • Viewport Maximum (vmax) – A percentage of the viewport width or height, whichever is larger10vmax will resolve to 10% of the current viewport height in portrait orientations, and 10% of the viewport width on landscape orientations. Sadly, and strangely, vmax units are not yet available on Internet Explorer or Edge.

While these units are derived from viewport height or width, they can all be used everywhere lengths are accepted – from font-size to positioning, margins, padding, shadows, borders, and so on. Let’s see what we can do!

Responsive Typography

It’s become very popular to use viewport units for responsive typography – establishing font-sizes that grow and shrink depending on the current viewport size. Using simple viewport units for font-size has an interesting (dangerous) effect. As you can see, fonts scale very quickly – adjusting from unreadably small to extra large in a very small range.

This direct scaling is clearly too dramatic for daily use. We need something more subtle, with minimums and maximums, and more control of the growth rate. That’s where calc() becomes useful. We can combine a base size in more steady units (say 16px) with a smaller viewport-relative adjustment (0.5vw), and let the browser do the math: calc(16px + 0.5vw)

By changing the relationship between your base-size and viewport-relative adjustment, you can change how dramatic the growth-rate is. Use higher viewport values on headings, and watch them grow more quickly than the surrounding text. This allows for a more dynamic typographic scale on larger screens, while keeping fonts constrained on a mobile device – no media-queries required. You can also apply this technique to your line-height, allowing you to adjust leading at a different rate than the font-size.

body {
  // font grows 1px for every 100px of viewport width
  font-size: calc(16px + 1vw);
  // leading grows along with font,
  // with an additional 0.1em + 0.5px per 100px of the viewport
  line-height: calc(1.1em + 0.5vw);
}

For me, this is enough complexity. If I need to constrain the top-end for rapid-growth headings, I can do that with one single media-query wherever the text becomes too large:

h1 {
  font-size: calc(1.2em + 3vw);
}

@media (min-width: 50em) {
  h1 {
    font-size: 50px;
  }
}

Suddenly I wish there was a max-font-size property.

Others have developed more complex calculations and Sass mixins to specify the exact text-size ranges at specific media-queries. There are several existing CSS-Tricks articles that explain the technique and provide snippets to help you get started:

I think that’s overkill in most cases, but your milage will absolutely vary.

Full-Height Layouts, Hero Images, and Sticky Footers

There are many variations on full-height (or height-constrained) layouts – from desktop-style interfaces to hero images, spacious designs, and sticky footers. Viewport-units can help with all of these.

In a desktop-style full-height interface, the page is often broken into sections that scroll individually – with elements like headers, footers, and sidebars that remains in place at any size. This is common practice for many web-apps these days, and vh units make it much simpler. Here’s an example using the new CSS Grid syntax:

See the Pen Full-height CSS Grid by Miriam Suzanne (@mirisuzanne) on CodePen.

A single declaration on the body element, height: 100vh, constrains your application to the height of the viewport. Make sure you apply overflow values on internal elements, so your content isn’t cut off. You can also achieve this layout using flexbox or floats.Note that full-height layouts can cause problems on some mobile browsers. There’s a clever fix for iOs Safari, that we use to handle one of the most noticeable edge-cases.

Sticky-footers can be created with a similar technique. Change your body height: 100vh to min-height: 100vh and the footer will stay in place at the bottom of your screen until it’s pushed down by content.

See the Pen Sticky-Footer with CSS Grid by Miriam Suzanne (@mirisuzanne) on CodePen.

Apply vh units to the height, min-height, or max-height of various elements to create full-screen sections, hero images, and more. In the new OddBird redesign, we constrained our hero images with max-height: 55vh so they never push headlines off the page. On my personal website, I went with max-height: 85vh for a more image-dominated look. On other sites, I’ve applied min-height: 90vh to sections.

Here’s an example showing both a max-height heroic kitten, and a min-height section. Combining all these tricks can give you some powerful control around how your content fills a browser window, and responds to different viewports.

Fluid Aspect Ratios

It can also be useful to constrain the height-to-width ratio of an element. This is especially useful for embeded content, like videos. Chris has written about this before. In the good-old-days, we would do that with %-based padding on a container element, and absolute positioning on the inner element. Now we can sometimes use viewport units to achieve that effect without the extra markup.

If we can count on the video being full-screen, we can set our height relative to the full viewport width:

/* full-width * aspect-ratio */
.full-width {
  width: 100vw;
  height: calc(100vw * (9/16));
}

That math doesn’t have to happen in the browser with calc. If you are using a pre-processor like Sass, it will work just as well to do the math there: height: 100vw * (9/16). If you need to constrain the max-width, you can constrain the max-height as well:

/* max-width * aspect-ratio */
.full-width {
  width: 100vw;
  max-width: 30em;
  height: calc(100vw * (9/16));
  max-height: calc(30em * (9/16));
}

Here’s a demonstration showing both options, with CSS custom properties (variables) to make the math more semantic. Play with the numbers to see how things move, keeping the proper ratio at all times:

See the Pen Fluid Ratios with Viewport Units by Miriam Suzanne (@mirisuzanne) on CodePen.

Chris takes this one step farther in his pre-viewport-units article, so we will too. What if we need actual HTML content to scale inside a set ratio – like presentation slides often do?

We can set all our internal fonts and sizes using the same viewport units as the container. In this case I used vmin for everything, so the content would scale with changes in both container height and width:

See the Pen Fluid Slide Ratios with Viewport Units by Miriam Suzanne (@mirisuzanne) on CodePen.

Breaking the Container

For years now, it’s been popular to mix constrained text with full-width backgrounds. Depending on your markup or CMS, that can become difficult. How do you break content outside of a restricted container, so that it fills the viewport exactly?

Again, viewport units can come in handy. This is another trick we’ve used on the new OddBird site, where a static-site generator sometimes limits our control of the markup. It only takes a few lines of code to make this work.

.full-width {
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

There are more in-depth articles about the technique, both at Cloud Four and here on CSS Tricks.

Getting Weird

Of course, there’s much more you can do with viewport units, if you start experimenting. Check out this pure CSS scroll-indicator (made by someone named Mike) using viewport units on a background image:

See the Pen CSS only scroll indicator by Mike (@MadeByMike) on CodePen.

What else have you seen, or done with viewport units? Get creative, and show us the results!


Fun with Viewport Units originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/fun-viewport-units/feed/ 23 252583
Loops in CSS Preprocessors https://css-tricks.com/loops-css-preprocessors/ https://css-tricks.com/loops-css-preprocessors/#comments Fri, 02 Dec 2016 12:50:39 +0000 http://css-tricks.com/?p=248524 If you’ve ever watched old sci-fi flicks, you know how powerful loops can be. Feed your robot nemesis an infinite loop, and kaboom. Robo dust.

Preprocessor loops will not cause dramatic explosions in space (I hope), but they are …


Loops in CSS Preprocessors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
If you’ve ever watched old sci-fi flicks, you know how powerful loops can be. Feed your robot nemesis an infinite loop, and kaboom. Robo dust.

Preprocessor loops will not cause dramatic explosions in space (I hope), but they are useful for writing DRY CSS. While everyone is talking about pattern libraries and modular design, most of the focus has been on CSS selectors. No matter what acronym drives your selectors (BEM, OOCSS, SMACSS, ETC), loops can help keep your patterns more readable and maintainable, baking them directly into your code.

We’ll take a look at what loops can do, and how to use them in the major CSS preprocessors: Sass, Less, and Stylus. Each language provides a unique syntax, but they all get the job done. There’s more than one way to loop a cat.

See the Pen
Walkcycle with music loop
by CSS-Tricks (@css-tricks)
on CodePen.

(animation by Rachel Nabors)

PostCSS is also popular, but it doesn’t provide any syntax of it’s own. While it’s sometimes called a post-processor, I’d call it a meta-preprocessor. PostCSS allows you to write and share your own preprocessor syntax. If you wanted, you could re-write Sass or Less inside PostCSS, but someone else beat you to it.

Loop Conditions

Star Trek wasn’t entirely wrong. If you’re not careful, infinite loops can slow down or crash your compiler. While that’s not a good way to vaporize evil robots, it will annoy anyone using your code. That’s why loops should always serve a limited purpose — usually defined by a number of incremental repetitions or a collection of objects.

In programming terms:

  1. While loops are generic, and will keep looping while any condition is met. Be careful! This is where infinite loops are most likely.
  2. For loops are incremental, running for a particular number of repetitions.
  3. For-Each loops iterate through a collection or list, considering each item one-at-a-time.

Each type of loop is more narrowly focussed than the previous. A for-each loop is just one type of for loop, which is one type of while loop. But most of your use-cases will fall into the more specific categories. I had trouble finding true while loops in the wild — most examples could have been handled better with for or for-each. That’s probably why Stylus only provides syntax for the latter. Sass provides unique syntax for all three, and Less doesn’t technically have looping syntax at all — but that won’t stop us! Let’s dive in.

Collection for-each Loops

Preprocessor loops are most useful when you have a collection (list or array) of items to loop over — like an array of social media icons and colors, or a list of state-modifiers (success, warning, error, etc). Because for-each loops are tied to a known collection of items, they tend to be the most concrete and understandable loops.

Let’s start by looping through a simple list of colors, to see how it works.

In Sass, we’ll use the @each directive (@each $item in $list) to get at the colors:

See the Pen Sass ForEach List by Miriam Suzanne (@mirisuzanne) on CodePen.

In Stylus, the for syntax (for item in list) handles collections:

See the Pen Stylus ForEach List by Miriam Suzanne (@mirisuzanne) on CodePen.

Less doesn’t provide a loop syntax, but we can fake it with recursion. Recursion is what happens when you call a function or mixin from inside itself. In Less, we can use mixins for recursion:

.recursion() {
  /* an infinite recursive loop! */
  .recursion();
}

Now we’ll add a when “guard” to the mixin, to keep it from looping infinitely.

.recursion() when (@conditions) { 
  /* a conditional recursive "while" loop! */
  .recursion();
}

We can make it a for loop by adding a counter (@i), which starts at 1, and increases with every repetition (@i + 1) as long as our condition (@i <= length(@list)) is met — where length(@list) restricts our loop-iterations to the same length as our collection. If we extract the next list item on each pass, we’ll have a hand-made for-each loop:

See the Pen Less ForEach List by Miriam Suzanne (@mirisuzanne) on CodePen.

In Less, you get to do everything the hard way. It builds character.

Social Media Buttons

Looping through lists can be useful, but more often you want to loop through objects. One common example is assigning different colors and icons to your social media buttons. For each item in the list, we’ll need the name of the site and the brand color for that social network:

$social: (
  'facebook': #3b5999,
  'twitter': #55acee,
  'linkedin': #0077B5,
  'google': #dd4b39,
);

Using Sass, we can access the key (network name) and value (brand color) of each pair using the syntax @each $key, $value in $array. Here’s the full loop:

See the Pen Sass Social Media Loop by Miriam Suzanne (@mirisuzanne) on CodePen.

Stylus has a similar syntax: for key, value in array

See the Pen Stylus Social Media Loop by Miriam Suzanne (@mirisuzanne) on CodePen.

In Less, we have to extract each side of the pair manually:

See the Pen LESS Social Media Loop by Miriam Suzanne (@mirisuzanne) on CodePen.

Incremental for Loops

For loops can run for any number of repetitions, not just the length of an object. You might use this to create a grid layout (for columns from 1 through 12), loop through the color wheel (for hue from 1 through 360), or number your divs with nth-child and generated content.

Let’s start with a loop through 36 div elements, providing a number and background-color on each, using :nth-child.

Sass provides a special for-loop syntax: @for $count from $start through $finish, where $start and $finish are both integers. If the starting value is larger, Sass will count down instead of up.

See the Pen Sass “for” loop by Miriam Suzanne (@mirisuzanne) on CodePen.

The through keyword means our loop will include the number 36. You can also use the to keyword, which does not include the final counter: @for $i from 1 to 36 would only loop 35 times.

Stylus has a similar syntax for incrementing, but to and through are replaced with ... and .. respectively:

See the Pen Stylus “for” loop by Miriam Suzanne (@mirisuzanne) on CodePen.

Stylus also provides a range() function, which allows you to change the size of one increment. Using for hue in range(0, 360, 10) would increase the count by 10 on each repetition.

Less will have to use recursive mixins again. We can create an argument for the number of iterations (@i), guard it with the condition when (@i > 0), and subtract one on every iteration — to make it act like a decreasing for-loop:

See the Pen Less “for” loop by Miriam Suzanne (@mirisuzanne) on CodePen.

It’s worth noting that CSS can also give us the nth-child-numbering without pre-processors. While CSS doesn’t have loop structures, it does provide a counter() that you can increment based on any number of DOM-related conditions, and use in generated content. Sadly it can’t be used outside the content property (yet), so our background-colors are not applied:

See the Pen CSS counter by Miriam Suzanne (@mirisuzanne) on CodePen.

Grid Systems

I use incremental loops occasionally in my abstract Sass toolkits, but almost never in actual stylesheets. The one common exception is generating numbered selectors, either with nth-child (like we did above) or in automatically-generated classes (often used for grid systems). Let’s build a simple fluid grid system without any gutters to make the math difficult:

See the Pen Sass For-Loop Grids by Miriam Suzanne (@mirisuzanne) on CodePen.

Each grid-span is a percentage, using the math span / context * 100% — the basic calculation all grid systems have to make. Here it is again in Stylus and Less:

See the Pen Stylus For-Loop Grids by Miriam Suzanne (@mirisuzanne) on CodePen.

See the Pen LESS For-Loop Grids by Miriam Suzanne (@mirisuzanne) on CodePen.

Unique Avatars

At OddBird, we recently designed an application with default user avatars — but we wanted the defaults to be as unique as possible. In the end, we designed only nine unique icons and used loops to transform them into 1296 different avatars, so most users would never see a duplicate.

Each avatar has five attributes:

<svg class="avatar" data-dark="1" data-light="2" data-reverse="true" data-rotation="3">
  <use xlink:href="#icon-avatar-1" xmlns:xlink="http://www.w3.org/1999/xlink"></use>
</svg>
  1. The starting icon shape (9 options)
  2. Rotatation of 0, 90, 180, or 270 degrees (4 options)
  3. A dark color for fill (6 options)
  4. A light color for background (6 options)
  5. A true/false attribute that reverses the colors (2 options)

The code has six colors, and three loops:

  1. @for $i from 0 through 3 gives us four rotations
  2. @for $i from 1 through length($colors) allows us to loop through the color list ($colors), and assign each color a number ($i). Normally I would use the @each loop to step through the collection of colors, but @for is simpler when I need a number for each item as well.
  3. The nested @each $reverse in (true, false) gives us the option to flip foreground and background for each color combination.

Here’s the final result in Sass:

See the Pen 1296 avatars using multiple loops by Miriam Suzanne (@mirisuzanne) on CodePen.

Converting that to Less and Stylus can be your homework. I’m already tired of looking at it.

Generic while Loops

True while loops are rare, but I do use them on occasion. I find them most useful when I’m following a path to see where it leads. I don’t want to loop through an entire collection or a specific number of iterations — I want to keep looping until I find what I’m looking for. This is something I use in my abstract toolkits, but not something you’ll need very often in day-to-day styling.

I built a toolkit to help me store and manipulate colors in Sass. Storing colors in variables might be the most common use-case for any pre-processor. Most people do something like this:

$pink: #E2127A;
$brand-primary: $pink;
$site-background: $brand-primary;

I know that pink is probably not the only color on your site, but it’s the only one e need for now. I gave it multiple names because it’s useful to establish layers of abstraction — from simple colors (pink), to broader patterns (brand-primary), and concrete use-cases (site-background). I also want to turn that list of individual colors into a palette that my pre-processor can understand. I need a way to say these values are all related, and part of a pattern. That way to do that, I store all my theme colors in a single Sass map, with key-value pairs:

$colors: (
  'pink': #E2127A,
  'brand-primary': 'pink',
  'site-background': 'brand-primary',
);

Why bother? I do it because I can point my style-guide generator at a single variable, and automatically create a color palette that stays up-to-date. But there are trade-offs, and it’s not the right solution for everyone. The map doesn’t allow me to make direct assignments across pairs, like I could with variables. I need a while loop to follow the breadcrumb trail of key-names, in order to find the value for each color:

See the Pen Sass “while” loop by Miriam Suzanne (@mirisuzanne) on CodePen.

I do that all the time, but if you search my code for Sass’s @while, you won’t find it. That’s because you can achieve the same thing with a recursive function, making it reusable:

See the Pen Sass “while” recursive function by Miriam Suzanne (@mirisuzanne) on CodePen.

Now we can call the color() function anywhere in our code.

Stylus has no syntax for while loops, but it does also allow array variables and recursive functions:

See the Pen Stylus “while” loop by Miriam Suzanne (@mirisuzanne) on CodePen.

Less doesn’t have array variables built-in, but we can mimic the same effect by creating a list of pairs, the same way we did for social-media colors:

@colors:
  'pink' #E2127A,
  'brand-primary' 'pink',
  'site-background' 'brand-primary'
;

We’ll have to create our own @array-get mixin to retrieve values from the array using key names, and then create our recursive while loop to follow the path:

See the Pen Less “while” list loop by Miriam Suzanne (@mirisuzanne) on CodePen.

That works for the purpose of demonstration, but there’s probably a better way to do this in Less, since you can alias and name-space variables without using an array (unlike Sass or Stylus):

See the Pen Less name-spaced variables by Miriam Suzanne (@mirisuzanne) on CodePen.

Now that my colors are successfully in one variable, I can use another loop to generate my color palette. Here’s a quick example in Sass:

See the Pen Sass color palette by Miriam Suzanne (@mirisuzanne) on CodePen.

I’m sure you could make that prettier than I did.

Getting Loopy!

If you’re not sure when to use loops in your code, keep an eye out for repetition. Do you have multiple selectors that follow a similar pattern, or a calculation you are doing over and over? Here’s how to tell which loop is best:

  1. If you can list and name the items in your loop, use for-each to cycle through them.
  2. If the number of repetitions is more important than any set of source items, or if you need your items numbered, use a for loop.
  3. If you’ll need to access the same loop with different inputs, try a recursive function instead.
  4. For anything else (almost never), use a while loop.
  5. If you’re using Less… Good luck!

Have fun looping!


Loops in CSS Preprocessors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/loops-css-preprocessors/feed/ 9 248524