Tag: Values

Multi-Value CSS Properties With Optional Custom Property Values

Imagine you have an element with a multi-value CSS property, such as transform: optional custom property values:

.el {   transform: translate(100px) scale(1.5) skew(5deg); }

Now imagine you don’t always want all the transform values to be applied, so some are optional. You might think of CSS optional custom property values:

.el {   /*         |-- default ---| |-- optional --| */   transform: translate(100px) var(--transform); }

But surprisingly using optional custom property values like this does not work as intended. If the --transform variable is not defined the whole property will not be applied. I’ve got a little “trick” to fix this and it looks like this:

.el {   transform: translate(100px) var(--transform, ); }

Notice the difference? There is a fallback defined in there that is set to an empty value: (, )

That’s the trick, and it’s very useful! Here’s what the specification has to say:

In an exception to the usual comma elision rules, which require commas to be omitted when they’re not separating values, a bare comma, with nothing following it, must be treated as valid in var(), indicating an empty fallback value.

This is somewhat spiritually related to the The CSS Custom Property Toggle Trick that takes advantage of a custom property having the value of an empty space.


Like I said, this is useful and works for any multi-value CSS property. The following demo shows it using text-shadow, background, and filter in addition to the transform example we just discussed.

See the Pen CSS var – Fallback To Nothing by Yair Even Or (@vsync) on CodePen.

Some properties that accept multiple values, like text-shadow, require special treatment because they only work with a comma delimiter. In those cases, when the CSS custom property is defined, you (as the code author) know it is only to be used in a situation where a value is already defined where the custom property is used. Then a comma should be inserted directly in the custom property before the first value, like this:

--text-shadow: ,0 0 5px black;

This, of course, inhibits the ability to use this variable in places where it’s the only value of some property. That can be solved, though, by creating “layers” of variables for abstraction purposes, i.e. the custom property is pointing to lower level custom properties.

Beware of Sass compiler

While exploring this trick, uncovered a bug in the Sass compiler that strips away the empty value (,) fallback, which goes against the spec. I’ve reported the bug and hope it will be fixed up soon.

As a temporary workaround, a fallback that causes no rendering can be used, such as:

transform: translate(100px) var(--transform, scale(1));

Multi-Value CSS Properties With Optional Custom Property Values originally published on CSS-Tricks. You should get the newsletter and become a supporter.


, , , , ,

The surprising behavior of !important in CSS custom property values

Huh! I did not realize that CSS custom properties had their own resolution behavior for how !important works in their values. Uh, despite writing a guide about them. 😬 But hey it’s now updated.

Stefan Judis documents it clearly. The deal is that !important is not at all used in the final value. So:

div {   --color: red !important;   color: var(--color);   color: yellow; }

It kinda feels like red should win, but because !important is ultimately stripped from the custom property value, yellow wins it out. And it’s not because the color declaration comes last — if color: red !important; was the first declaration, then red would win.

But it’s not like !important is just stripped and ignored; it’s used in a scoped way, affecting which custom property value wins. But then !important is gone when that value is applied. Stefan’s example:

div {   /*     `!important` overrules the     other `--color` definitions   */   --color: red !important;   color: var(--color); }  .class {   --color: blue; }  #id {   --color: yellow; }

This feels weird (to me) as you’d think yellow declared on #id would win because it has the highest specificity. But that’s the deal with the scoped behavior — !important makes red the winner, then is applied to the color as such.

To Shared LinkPermalink on CSS-Tricks


, , , , ,

CSS Logical Properties and Values

Now that cross-browser support is at a tipping point, it’s a good time to take a look at logical properties and values. If you’re creating a website in multiple languages, logical properties and values are incredibly useful. Even if you’re not, there are still some convenient new shorthands it’s worth knowing about.

For example, I’ve lost count of the amount of times I’ve written this to center something:

.thing {   margin-left: auto;   margin-right: auto; }

We could make it a one-liner with something like margin: 0 auto; but then the top and bottom margins get thrown into the mix. Instead, we can select just the left and right margin with the margin-inline logical property.

Start thinking of things as “inline” or “block”

That last demo is pretty neat, right? The margin-inline property sets both margin-left and margin-right. Similarly, the margin-block property sets both margin-top and margin-bottom. And we’re not only talking margins. Logical properties has similar shorthands to set border and padding. So if you have a visual design that calls for borders only on the sides, you can just use border-inline instead of fussing with each physical direction on its own.

Showing border-left and border-right with matching values combined together as border-inline as a single declaration, and another example showing padding-top and padding-bottoms et to 32 pixels combined to padding-block set to 32 pixels.
Rather than thinking in physical terms, like left and right, we can think of an “inline” direction and a “block” direction.

So, as we move ahead, we now know that we’re dealing with inline and block directions instead of physical directions. Inline handles the left and right directions, while block manages top and bottom.

That is, until things get swapped around when the writing-mode changes.

Pay attention to direction and writing mode

What we’ve seen so far are examples of CSS logical properties. These are versions of CSS properties were used to like margin and padding, but written in a new way that forgoes physical directions (i.e. left, right, top, and bottom).

CSS was developed with the English language in mind and English is written and read from left-to-right. That’s not the case for all languages though. Arabic, for example, is read from right-to-left. That’s why HTML has a dir attribute.

<html dir="rtl">

CSS has an equivalent property (although it’s recommended to use the HTML attribute just in case the CSS fails to load):

.foreign-language { direction: rtl; }
Two cards, one in english and one in arabic, Both cards have a subtitle in gray above a main heading in a larger black font. The english goes from left to right and indicates the direction with an arrow below the card. The arabic direction is reverse of the english.
Credit: Ahmad Shadeed

Chinese, Japanese, Korean and Mongolian can be written either horizontally from left-to-right, or vertically from top to bottom. The majority of websites in these languages are written horizontally, the same as with English.

Comparatively, vertical writing is more common on Japanese websites. Some sites use a mixture of both vertical and horizontal text.


When written vertically, Chinese, Japanese and Korean are written with the top-right as a starting point, whereas Mongolian reads from left to right. This is exactly why we have the writing-mode property in CSS, which includes the following values:

  • horizontal-tb: This is the default value, setting the the direction left-to-right for languages like English or French, and right-to-left languages like Arabic. The tb stands for “top-to-bottom.”
  • vertical-rl: This changes the direction to right-to-left in a vertical orientation for languages like Chinese, Japanese and Korean.
  • vertical-lr: This is used for vertical left-to-right languages, like Mongolian.

CSS logical properties offer a way to write CSS that is contextual. When using logical properties, spacing and layout are dependent on both the writing-mode and direction (whether set by either CSS or HTML). It therefore becomes possible to reuse CSS styles across different languages. BBC News, for example, rebuild their website in over a dozen languages. That’s a better experience than leaving users to rely on autotranslate. It also means they can better cater specific content to different parts of the world. The visual styling though, remains much the same across regions.

Screenshot of the BBC website. The header is red with the BBC logo aligned to the right of the screen. The navigation is also in red and aligned to the right. There is a featured article with right-aligned text and a large image to the right of it. Below that are four more article cards in a single row, each with an image above a title and date and aligned right.

Let’s look at the example below to see the shortcomings of physical properties. Using the physical margin-left property (shown in red), everything looks good in English. If you were to reuse the CSS but change the writing mode to rtl (shown at the bottom) there’s no space between the text and the icon and there’s excess white space on the left of the text. We can avoid this by using a logical property instead.

Two buttons, both with an envelope icon and a label. The left-to-right version of the button on top shows the spacing between the icon and the label. The right-to-left version shows the spacing to the left of both the label and icon.

What makes logical properties and values so useful is that they will automatically cater to the context of the language. In a left-to-right language like English, margin-inline-start will set the left-side margin. For a right-to-left language like Arabic, Urdu, or Hebrew, it will set the right-hand margin — which solves the layout problem in the above example. That’s right-to-left taken care of. If you have vertical text, margin-inline-start will cater to that context to, adding the margin at the top, which is where you would start reading from in any vertical language (that’s why it’s called margin-inline-start — just think about which direction you start reading from). The direction of inline changes based on the element’s writing-mode. When a vertical writing-mode is set, it handles the vertical direction top and bottom. See how things can get switched around?

An example of the writing direction in Mongolian. (Credit: W3C)

A complete list of logical properties and values

There are dozens of CSS properties that have a logical alternative syntax. Adrian Roselli has a handy visualization where you can toggle between the physical CSS properties that we’re all used to and their logical property equivalents. It’s a nice way to visualize logical properties and the physical properties they map to when the direction is ltr and the writing-mode is horizontal-tb.

Let’s break all of those down even further and map each and every physical CSS property to its logical companion, side-by-side. The tables shown throughout this article show traditional physical CSS in the left column and their logical equivalents (using a left-to-right horizontal mapping) in the right column. Remember though, the whole point of logical properties is that they change based on context!


In a horizontal writing mode, inline-size sets the width of an element, while block-size sets the height. In a vertical writing mode, the opposite is true: inline-size sets the height and block-size sets the width.

Physical property Logical property
width inline-size
max-width max-inline-size
min-width min-inline-size
height block-size
max-height max-block-size
min-height min-block-size

Logical properties for sizing have good cross-browser support.


Everything here has solid cross-browser support among modern browsers.

Physical property Logical property
border-top border-block-start
border-bottom border-block-end
border-left border-inline-start
border-right border-inline-end

Here’s an example of using border-inline-start shown with English, Arabic, and Chinese.

Here’s an example that sets border-block-start dotted and border-block-end dashed:

There are also logical properties for setting the border color, width, and style individually:

Physical property Logical property
border-top-color border-block-start-color
border-top-width border-block-start-width
border-top-style border-block-start-style

So, again, it’s about thinking in terms of “inline” and “block” instead of physical directions, like left and top. We also have shorthand logical properties for borders:

Physical property Logical property
border-top and border-bottom border-block
border-left and border-right border-inline


Here are all the individual logical margin properties:

Physical property Logical property
margin-top margin-block-start
margin-bottom margin-block-end
margin-left margin-inline-start
margin-right margin-inline-end

These logical properties has comprehensive modern cross-browser support, including Samsung Internet, and has been supported in Safari since 12.2.

And, remember, we have the shorthands as well:

Physical property Logical property
margin-top and margin-bottom margin-block
margin-left and margin-right margin-inline


Padding is super similar to margin. Replace margin with padding and we’ve got the same list of properties.

Physical property Logical property
padding-top padding-block-start
padding-bottom padding-block-end
padding-left padding-inline-start
padding-right padding-inline-end
padding-top and padding-bottom padding-block
padding-left and padding-right padding-inline

Just like margins, logical properties for padding have good cross-browser support.


Need to offset an element’s position in a certain direction? We can declare those logically, too.

Physical property Logical property
top inset-block-start
bottom inset-block-end
left inset-inline-start
right inset-inline-end
top and bottom inset-block
left and right inset-inline

In a horizontal writing mode (either left-to-right, or right-to-left) inset-block-start is equivalent to setting top, and inset-block-end is equivalent to setting bottom. In a horizontal writing mode, with a left-to-right direction, inset-inline-start is equivalent to left, while inset-inline-end is equivalent to right, and vice-versa for right-to-left languages.

Conversely, for a vertical writing mode, inset-inline-start is equivalent to top while inset-inline-end is equivalent to bottom. If writing-mode is set to vertical-rl, inset-block-start is equivalent to right and inset-block-end is equivalent to left. If the writing-mode is set to vertical-lr, the opposite is the case and so inset-block-start is equivalent to left.

Logical property Writing mode Equivalent to:
inset-block-start` Horizontal LTR top
inset-block-start Horizontal RTL top
inset-block-start Vertical LTR left
inset-block-start Vertical RTL right

Here’s an example of how the same CSS code for absolute positioning looks in each of the four different writing directions:

Logical properties for positioning are supported in all modern browsers, but only recently landed in Safari.

There’s also a new shorthand for setting all four offsets in one line of code. Here’s an example using inset as a shorthand for setting top, bottom, left, and right in one fell swoop to create a full-page overlay:

I’ve heard inset incorrectly referred to as a logical property. But, a quick look in DevTools shows that it is actually a shorthand for physical values, not logical properties:

What it’s actually doing is defining physical offsets (i.e. left, right, top and bottom) rather than logical ones (i.e. inline, block, start and end). Obviously if you want to set the same value for all four sides, as in the example above, it doesn’t matter.

inset: 10px 20px 5px 8px; /* shorthand for physical properties not logical properties  */

Text alignment

Logical values for text alignment enjoy great browser support and have for many years. When working in English, text-align: start is the same as text-align: left, while text-align: end is the same as text-align-right. If you set the dir attribute to rtl, they switch and text-align: start aligns text to the right.

Physical value Writing mode Equivalent to:
start LTR left
start RTL right
end LTR right
end RTL left

Border radius

So far everything we’ve looked at has decent browser support. However, there are some other logical properties where support is still a work in progress, and border radius is one of them. In other words, we can set a different border-radius value for different corners of an element using logical properties, but browser support isn’t great.

Physical property Logical property
border-top-left-radius border-start-start-radius
border-top-right-radius border-start-end-radius
border-bottom-left-radius border-end-start-radius
border-bottom-right-radius border-end-end-radius

It’s worth noting that the spec doesn’t include shorthand properties, like border-start-radius and border-end-radius. But, like I said, we’re still in early days here, so that might be a space to watch.


Flow-relative values for logical floats have terrible browser support at the time I’m writing this. Only Firefox supports inline-start and inline-end as float values.

Physical value Logical value
float: left float: inline-start
float: right float: inline-end
clear: left clear: inline-start
clear: right clear: inline-end

Other logical properties

There are proposed logical properties for overflow and resize, but they currently have horrendous browser support.

Physical Logical
resize: vertical resize: block
resize: horizontal resize: inline
overflow-y overflow-block
overflow-x overflow-inline

Digging deeper

We explored what it means for a property to be considered “logical” and then mapped out all of the new logical properties and values to their physical counterparts. That’s great! But if you want to go even deeper into CSS Logical Properties and Values, there are a number of resources worth checking out.

  • “RTL Styling 101” (Ahmad Shadeed): A great resource if you’re dealing with Arabic or other right-to-left languages. Ahmad covers everything, from logical properties to considerations when working with specific layout techniques, like flexbox and grid.
  • text-combine-upright (CSS-Tricks): If you’re dealing with vertical text, did you know that this property can rotate text and squeeze multiple characters into the space of a single character? It’s a nice touch of refinement in specific situations where some characters need to go together but still flow with a vertical writing mode.

If you want to view some nice real-world examples of vertical typography from across the web, take a look at the Web Awards for Horizontal and Vertical Writings. There’s a lot of great stuff in there.

Wrapping up

Do you need to rush and swap all of the physical properties out of your codebase? Nope. But it also doesn’t hurt to start using logical properties and values in your work. As we’ve seen, browser support is pretty much there. And even if you’re working on a site that’s just in English, there’s no reason to not use them.

The post CSS Logical Properties and Values appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, ,

Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values

In this week’s round-up, prefers-contrast lands in Safari, MathML gets some attention, :is() is actually quite forgiving, more ADA-related lawsuits, inconsistent initial values for CSS Backgrounds properties can lead to unwanted — but sorta neat — patterns.

The prefers-contrast: more media query is supported in Safari Preview

After prefers-reduced-motion in 2017, prefers-color-scheme in 2019, and forced-colors in 2020, a fourth user preference media feature is making its way to browsers. The CSS prefers-contrast: more media query is now supported in the preview version of Safari. This feature will allow websites to honor a user’s preference for increased contrast.

Screenshot of the iPhone 12 landing page on Apple's website. A big red arrow points out light grey text on the page.
Apple could use this new media query to increase the contrast of gray text on its website
.pricing-info {   color: #86868b; /* contrast ratio 3.5:1 */ }  @media (prefers-contrast: more) {   .pricing-info {     color: #535283; /* contrast ratio 7:1 */   } }

Making math a first-class citizen on the web

One of the earliest specifications developed by the W3C in the mid-to-late ’90s was a markup language for displaying mathematical notations on the web called MathML. This language is currently supported in Firefox and Safari. Chrome’s implementation was removed in 2013 because of “concerns involving security, performance, and low usage on the Internet.”

If you’re using Chrome or Edge, enable “Experimental Web Platform features” on the about:flags page to view the demo.

There is a renewed effort to properly integrate MathML into the web platform and bring it to all browsers in an interoperable way. Igalia has been developing a MathML implementation for Chromium since 2019. The new MathML Core Level 1 specification is a fundamental subset of MathML 3 (2014) that is “most suited for browser implementation.” If approved by the W3C, a new Math Working Group will work on improving the accessibility and searchability of MathML.

The mission of the Math Working Group is to promote the inclusion of mathematics on the Web so that it is a first-class citizen of the web that displays well, is accessible, and is searchable.

CSS :is() upgrades selector lists to become forgiving

The new CSS :is() and :where() pseudo-classes are now supported in Chrome, Safari, and Firefox. In addition to their standard use cases (reducing repetition and keeping specificity low), these pseudo-classes can also be used to make selector lists “forgiving.”

For legacy reasons, the general behavior of a selector list is that if any selector in the list fails to parse […] the entire selector list becomes invalid. This can make it hard to write CSS that uses new selectors and still works correctly in older user agents.

In other words, “if any part of a selector is invalid, it invalidates the whole selector.” However, wrapping the selector list in :is() makes it forgiving: Unsupported selectors are simply ignored, but the remaining selectors will still match.

Unfortunately, pseudo-elements do not work inside :is() (although that may change in the future), so it is currently not possible to turn two vendor-prefixed pseudo-elements into a forgiving selector list to avoid repeating styles.

/* One unsupported selector invalidates the entire list */ ::-webkit-slider-runnable-track, ::-moz-range-track {   background: red; }  /* Pseudo-elements do not work inside :is() */ :is(::-webkit-slider-runnable-track, ::-moz-range-track) {   background: red; }  /* Thus, the styles must unfortunately be repeated */ ::-webkit-slider-runnable-track {   background: red; } ::-moz-range-track {   background: red; }

Dell and Kraft Heinz sued over inaccessible websites

More and more American businesses are facing lawsuits over accessibility issues on their websites. Most recently, the tech corporation Dell was sued by a visually impaired person who was unable to navigate Dell’s website and online store using the JAWS and VoiceOver screen readers.

The Defendant fails to communicate information about its products and services effectively because screen reader auxiliary aids cannot access important content on the Digital Platform. […] The Digital Platform uses visual cues to convey content and other information. Unfortunately, screen readers cannot interpret these cues and communicate the information they represent to individuals with visual disabilities.

Earlier this year, Kraft Heinz Foods Company was sued for failing to comply with the Web Content Accessibility Guidelines on one of the company’s websites. The complaint alleges that the website did not declare a language (lang attribute) and provide accessible labels for its image links, among other things.

In the United States, the Americans with Disabilities Act (ADA) applies to websites, which means that people can sue retailers if their websites are not accessible. According to the CEO of Deque Systems (the makers of axe), the recent increasing trend of web-based ADA lawsuits can be attributed to a lack of a single overarching regulation that would provide specific compliance requirements.

background-clip and background-origin have different initial values

By default, a CSS background is painted within the element’s border box (background-clip: border-box) but positioned relative to the element’s padding box (background-origin: padding-box). This inconsistency can result in unexpected patterns if the element’s border is semi-transparent or dotted/dashed.

A pink and triple rectangle with rounded edges. The colors overlap in a pattern.
.box {   /* semi-transparent border */   border: 20px solid rgba(255, 255, 255, 0.25);    /* background gradient */   background: conic-gradient(     from 45deg at bottom left,     deeppink,     rebeccapurple   ); }

Because of the different initial values, the background gradient in the above image is repeated as a tiled image on all sides under the semi-transparent border. In this case, positioning the background relative to the border box (background-origin: border-box) makes more sense.

The post Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , , ,

Can you get valid CSS property values from the browser?

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That’s like this.

That gives you, for example, the fact that cursor is a thing. But then how do you know what valid values are for cursor? We know from documentation that there are values like auto, none, help, context-menu, pointer, progress, wait, and many more.

But where does that list come from? Well, there is a list right in the spec so that’s helpful. But that doesn’t guarantee the complete list of values that any given browser actually supports. There could be cursor: skull-and-crossbones and we wouldn’t even know!

We can test by applying it to an element and looking in DevTools:


But unless we launch a huge dictionary attack against that value, we don’t actually know what values it directly in-browser. Maybe Houdini will help somehow in browsers getting better at CSS introspection?

You can also use the CSS object to run tests like CSS.supports(property, value):


You’d think we could have like CSS.validValues("text-decoration-thickness") and get like ["<length>", "<percentage>", "auto", "from-font"] or the like, but alas, not a thing.

The post Can you get valid CSS property values from the browser? appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Computed Values: More Than Meets the Eye

Browser DevTools are indispensable for us front end developers. In this article, we’ll take a look at the Computed tab, a small corner of the DevTools panel that shows us big things, like how relative CSS values are resolved. We’ll also see how inheritance fits into the browser’s style computation process.

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

The content in the Computed tab is important because it shows us the values that the browser is actually using on the rendered website. If an element isn’t styled how you think it should be, looking at its computed values can help you understand why.

If you’re more accustomed to using the Styles tab (called Rules in Firefox), you may wonder how it differs from the Computed tab. I mean, they both show styles that apply to an element. The answer? The Computed tab displays an alphabetized list of resolved styles that include what is declared in your stylesheet, those derived from inheritance, and the browser’s defaults.

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

The Styles tab, on the other hand, displays the exact rulesets of a selected element exactly as they were written. So while the Styles tab might show you something like .subhead {font-size: 75%}, the Computed tab will show you the actual font size, or what 70% currently resolves to. For example, the actual font size for the rendered text as shown above is 13.2px.

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

Next, let’s briefly review the concepts of inheritance and the cascade, two things that are a huge part of how the computed values in the Computed tab are arrived at.

Crash course on inheritance and the cascade

CSS stands for Cascading Style Sheets, and that first word cascading is incredibly important to understand – the way that the cascade behaves is key to understanding CSS.


The cascade is notable because it’s the “C” in CSS. It’s the mechanism used for resolving conflicts that exist between the different sources of style declarations for a document.

For example, imagine a stylesheet that defines the width of a div twice:

div {   width: 65vw; } 
 /* Somewhere, further down */ div {   width: 85vw; }

In this specific example, the second width wins out since it is declared last. The first width could still win with !important but that’s technically breaking the cascade by brute force. The point here is that the cascade algorithm determines what styles apply to each element and prioritizes them in a predetermined order to settle on a value.

The cascade is applied for properties that are set explicitly, whether by the browser, the web developer, or the user. Inheritance comes into the picture when the output of the cascade is empty. When this happens, the computed value of a property on an element’s parent is pulled in as its own value for that property. For example, if you specify a color for an element, all child elements will inherit that color if you don’t specify theirs.

There are four key property values related to inheritance that we should get acquainted with before we plow ahead. We’ll be using these throughout the article.


In an HTML document where the highest level of the DOM tree is the <html> element, when we use the initial keyword on an element like this…

…the text color for that element is black, even though the body element is set to green. There’s the matter of the div selector having a higher specificity, however we’re interested in why initial translated to black.

In plain terms, this keyword sets the default value of a property as specified in its definition table (in the CSS specs). In this case, black happens to be the browser’s implementation of the initial color value.

I mention near the end of the article that you can learn whether or not a property is inherited by default by checking out its page on MDN. Well, you can also find the initial value for any property this way.


For non-inherited properties, this keyword forces inheritance. In the following example, the <body> element has a solid red border. The border property isn’t inherited by default, but we can tell our div to inherit the same red border declared on the <body> element by using the inherit keyword on its border property:


unset will resolve to an inherited value if a property is inherited. Otherwise, the initial value is used. This basically means unset resets a property based on whether it is inherited or not. Here’s a demo that toggles unset to show its effect on elements with different levels of specificity.


If no CSS properties are set on an element, then does it get any styles at all? You bet. It uses the browser’s default styles.

For example, the initial value for the display property for span elements is inline, but we can specify it as block in our stylesheet. Use the button in the following demo to toggle revert on both the span element’s display and color properties:

The span properly reverts to an inline element, but wait! Did you notice that the color of the span goes to a green color instead of the browser’s default black value? That’s because revert allows for inheritance. It will go as far back as the browser’s default to set the color, but since we’ve explicitly set a green color on the <body> element, that’s what is inherited.

Finding computed values in DevTools 

This is where we start talking about the computed values in DevTools. Just as with the default values of properties, the computed value of a CSS property is determined by that property’s definition table in the CSS specifications. Here’s what that looks like for the height property.

Say we use relative lengths in our CSS, like one of 10em or 70% or 5vw. Since these are “relative” to something font-size or the viewport they’ll need to get resolved to a pixel-absolute value. For example, an element with a 10% width may compute to 100px if the viewport is 1000px wide, but some other number altogether when the viewport width changes.

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

These values are calculated whenever the DOM is modified in a process called computed styles calculation. This is what lets the browser know what styles to apply to each page element.

Style calculations happen in multiple steps involving several values. These are documented in the CSS Cascading and Inheritance Level 4 specification and they all impact the final value we see in the Computed tab. Let’s take a look at those next.

Values and how they’re processed

The values defined for the style calculation process include the declared value, the specified value, the cascaded value, the computed value, the used value, and the actual value. Who knew there were so many, right?

Declared values

A declared value is any property declaration applies to an element. A browser identifies these declarations based on a few criteria, including:

  • the declaration is in a stylesheet that applies to the current document
  • there was a matching selector in a style declaration
  • the style declaration contains valid syntax (i.e, valid property name and value)

Take the following HTML:

<main>   <p>It's not denial. I'm just selective about the reality I accept.</p> </main>

Here are declared values that apply to the font-size of the text:

main {   font-size: 1.2em; /* this would apply if the paragraph element wasn't targeted specifically, and even then, as an inherited value, not "declared value" */ } 
 main > p {   font-size: 1.5em; /* declared value */ }

Cascaded values

The list of all declared values that apply to an element are prioritized based things like these to return a single value:

  • origin of the declaration (is it from the browser, developer, or another source?)
  • whether or not the declaration is marked ‘!important’
  • how specific a rule is (e.g, span {} vs section span {})
  • order of appearance (e.g, if multiple declarations apply, the last one will be used)

In other words, the cascaded value is the “winning” declaration. And if the cascade does not result in a winning declared value, well, then there is no cascaded value.

main > p  {   font-size: 1.2em; } 
 main > .product-description { /* the same paragraph targeted in the previous rule */   font-size: 1.2em; /* cascaded value based on both specificity and document order, ignoring all other considerations such as origin */ }

Specified values

As mentioned earlier, it is possible for the output of the cascade to be empty. However, a value still needs to be found by other means.

Now, let’s say we didn’t declare a value for a specific property on an element, but did for the parent. That’s something we often do intentionally because there’s no need to set the same value in multiple places. In this case, the inherited value for the parent is used. This is called the specified value.

In many cases, the cascaded value is also the specified value. However, it can also be an inherited value if there is no cascaded value and the property concerned is inherited, whether by default or using the inherit keyword. If the property is not inherited, then the specified value is the property’s initial value, which, as mentioned earlier, can also be set explicitly using the initial keyword.

In summary, the specified value is the value we intend to use on an element, with or without explicitly declaring it on that element. This is a little murky because the browser’s default can also become the specified value if nothing is declared in the stylesheet.

/* Browser default = 16px */ 
 main > p {   /* no declared value for font-size for the paragraph element and all its ancestors */ }

Computed values

Earlier, we discussed, briefly, how relative values needed to be resolved to their pixel-absolute equivalent. This process, as already noted, is pre-determined. For example, property definition tables have a “Computed value” field that detail how specified values, in general, are resolved.

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

In the following example, we’re working with the em, a relative unit. Here, the final value used when rendering the element to which the property applies is not a fixed number as seen in our declared value, but something that needs to be calculated based on a few factors.

main {   font-size: 1.2em; } 
 main > p {   font-size: 1.5em; /* declared value */ }

The font-size of the paragraph element is set to 1.5em, which is relative to the font-size value of the main element, 1.2em. If main is a direct child of the body element – and no additional font-size declarations are made above that, such as by using the :root selector – we can assume that the calculation for the paragraph’s font-size will follow this approximate course:

Browser_Default_FontSize = 16px; Calculated_FontSize_For_Main = 1.2 * Browser_Default_FontSize; // 19.2px Calculated_FontSize_For_Paragraph = 1.5 * Calculated_FontSize_For_Main; // 28.8px

That 28.8px is the computed value. Here’s a demo:

Open up DevTools and check out the computed font sizes in the Computed tab.

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

Let’s say we’re using rem units instead:

html {   font-size: 1.2em; } 
 main {   font-size: 1.5rem; } 
 div {   font-size: 1.7rem; }

The computed value of a rem unit is based on the font-size of the root HTML element, so that means that the calculation changes a little bit. In this specific case, we’re using a relative unit on the HTML element as well, so the browser’s default font-size value is used to calculate the base font-size we’ll use to resolve all our rem values.

Browser_Default_FontSize = 16px Root_FontSize = 1.2 * Browser_Default_FontSize; // 19.2px Calculated_FontSize_For_Main = 1.5 * Root_FontSize; // 28.8px Calculated_FontSize_For_Div = 1.7 * Root_FontSize; // 32.64px

Open up DevTools again for this demo:

The value, 16px, for Browser_Default_FontSize is commonly used by browsers, but this is subject to variation. To see your current default, select the <html> element in DevTools and check out the font-size that is shown for it. Note that if a value was set for the root element explicitly, just as in our example, you may have to toggle it off in the Rules tab. Next, toggle on the “Show all” or “Browser styles” (Firefox) checkbox in the Computed tab to see the default.

During inheritance, computed values are passed down to child elements from their parents. The computation process for this takes into account the four inheritance-controlling keywords we looked at earlier. In general, relative values become absolute (i.e. 1rem becomes 16px). This is also where relative URLs become absolute paths, and keywords such as bolder (value for the font-weight property) get resolved. You can see some more examples of this in action in the docs.

Used values

The used value is the final result after all calculations are done on the computed value. Here, all relative values are turned absolute. This used value is what will be applied (tentatively) in page layout. You might wonder why any further calculations have to happen. Wasn’t it all taken care of at the previous stage when specified values were processed to computed values?

Here’s the thing: some relative values will only be resolved to pixel-absolutes at this point. For example, a percentage-specified width might need page layout to get resolved. However, in many cases, the computed value winds up also being the used value.

Note that there are cases where a used value may not exist. According to the CSS Cascading and Inheritance Level 4 specification:

…if a property does not apply to an element, it has no used value; so, for example, the flex property has no used value on elements that aren’t flex items.

Actual values

Sometimes, a browser is unable to apply the used value straightaway and needs to make adjustments. This adjusted value is called the actual value. Think of instances where a font size needs to be tweaked based on available fonts, or when the browser can only use integer values during rendering and need to approximate non-integer values.

Inheritance in browser style computations

To recap, inheritance controls what value is applied to an element for a property that isn’t set explicitly. For inherited properties, this value is taken from whatever is computed on the parent element, and for non-inherited properties, the initial value for that property is set (the used value when the keyword initial is specified).

We talked about the existence of a “computed value” earlier, but we really need to clarify something. We discussed computed values in the sense of one type of value that takes part in the style resolution process, but “computed value” is also a general term for values computed by the browser for page styling. You’ll typically understand which kind we mean by the surrounding context.

Only computed values are accessible to an inherited property. A pixel-absolute value such as 477px, a number such as 3, or a value such as left (e.g. text-align: left) is ready for the inheritance process. A percentage value like 85% is not. When we specify relative values for properties, a final (i.e. “used”) value has to be calculated. Percentage values or other relative values will be multiplied by a reference size (font-size, for instance) or value (e.g. the width of your device viewport). So, the final value for a property can be just what was declared or it might need further processing to be used.

You may or may not have already noticed, but the values shown in the Computed tab of the browser will not necessarily be the computed values we discussed earlier (as in computed vs. specified or used values). Rather, the values shown are the same as returned by the getComputedStyle() function. This function returns a value which, depending on the property, will either be the computed value or the used value.

Now, let’s see some examples.

Color inheritance

main {   color: blue; }  /* The color will inherit anyway, but we can be explicit too: */ main > p {   color: inherit; }

The value computed for the color property on the main element will be blue. As color is inherited by default, we really didn’t need color: inherit for the paragraph child element because it would wind up being blue anyway. But it helps illustrate the point.

Color values undergo their own resolution process to become used values.

Font size inheritance

main {   font-size: 1.2em; }  main > p {   /* No styles specified */ }

As we saw earlier in the section on values and how they are processed, our relative value for font-size will compute to an absolute value and then be inherited by the paragraph element, even if we don’t explicitly declare it (again, font-size is inherited by default). If we had previously set styles via a global paragraph element selector, then the paragraph may gain some extra styles by virtue of the cascade. Any property values that may be inherited will be, and some properties for which the cascade and inheritance didn’t produce a value will be set to their initial value.

Percentage-specified font size inheritance

body {   font-size: 18px; }  main {   font-size: 80%; }  main > p {   /* No styles specified */ }

Similar to the previous example, the <main> element’s font-size will be absolutized in preparation for inheritance and the paragraph will inherit a font-size that is 80% of the body’s 18px value, or 14.4px.

Forced inheritance and post-layout computation

Computed values generally resolve the specified value as much as possible without layout, but as mentioned earlier, some values can only be resolved post-layout, such as percentage-specified width values. Although width isn’t an inherited property, we can force inheritance for the purpose of illustrating pre-layout and post-layout style resolution.

This is a contrived example but what we’re doing is taking an element out of the page layout by setting its display property to none. We have two divs in our markup that inherit a width, 50%, from their parent element <section>. In the Computed tab in DevTools, the computed width for the first div is absolute, having been resolved to a pixel value (243.75px for me). On the other hand, the width of the second div that was taken out of the layout using display: none is still 50%.

We’ll imagine that the specified and computed value for the parent <section> element is 50% (pre-layout) and the used value is as shown under the Computed tab – that’s 487.5px for me, post-layout. This value is halved for inheritance by the child divs (50% of the containing block).

These values have to be computed whenever the width of the browser’s viewport changes. So, percentage-specified values become percentage-computed values, which become pixel-used values.

Properties that inherit by default

How do you know if a property inherits by default or not? For each CSS property in the MDN docs, there is a specifications section that provides some extra details that include whether or not the property is inherited. Here’s what that looks like for the color property:

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

Which properties are inherited by default and which aren’t is largely down to common sense.


Another reference option is the properties section of the W3C specs. Still another is this StackOverflow thread which may not be exhaustive at the time of writing.

Here are some examples of properties that inherit by default:

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

Hopefully this gives you a solid idea of how browsers compute styles and how to reference them in DevTools. As you can see, there’s a lot that goes into a value behind the scenes. Having that context goes a long way in helping you troubleshoot your work as well as furthering your general understanding of the wonderful language we know as CSS.

Further reading

The post Computed Values: More Than Meets the Eye appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Resizing Values in Steps in CSS

There actually is a steps() function in CSS, but it’s only used for animation. You can’t, for example, tell an element it’s allowed to grow in height but only in steps of 10px. Maybe someday? I dunno. There would have to be some pretty clear use cases that something like background-repeat: space || round; doesn’t already handle.

Another way to handle steps would be sequential media queries.

@media (max-width: 1500px) { body { font-size: 30px; }} @media (max-width: 1400px) { body { font-size: 28px; }} @media (max-width: 1300px) { body { font-size: 26px; }} @media (max-width: 1200px) { body { font-size: 24px; }} @media (max-width: 1000px) { body { font-size: 22px; }} @media (max-width: 900px) { body { font-size: 20px; }} @media (max-width: 800px) { body { font-size: 18px; }} @media (max-width: 700px) { body { font-size: 16px; }} @media (max-width: 600px) { body { font-size: 14px; }} @media (max-width: 500px) { body { font-size: 12px; }} @media (max-width: 400px) { body { font-size: 10px; }} @media (max-width: 300px) { body { font-size: 8px; }}

That’s just weird, and you’d probably want to use fluid typography, but the point here is resizing in steps and not just fluidity.

I came across another way to handle steps in a StackOverflow answer from John Henkel a while ago. (I was informed Star Simpson also called it out.) It’s a ridiculous hack and you should never use it. But it’s a CSS trick so I’m contractually obliged to share it.

The calc function uses double precision float. Therefore it exhibits a step function near 1e18… This will snap to values 0px, 1024px, 2048px, etc.

calc(6e18px + 100vw - 6e18px);

That’s pretty wacky. It’s a weird “implementation detail” that hasn’t been specced, so you’ll only see it in Chrome and Safari.

You can fiddle with that calculation and apply the value to whatever you want. Here’s me tuning it down quite a bit and applying it to font-size instead.

Try resizing that to see the stepping behavior (in Chrome or Safari).

The post Resizing Values in Steps in CSS appeared first on CSS-Tricks.


, ,

Datalist is for suggesting values without enforcing values

Have you ever had a form that needed to accept a short, arbitrary bit of text? Like a name or whatever. That’s exactly what <input type="text"> is for. There are lots of different input types (and modes!), and picking the right one is a great idea.

But this little story is about something else and applies to any of them.

What if the text needs to be arbitrary (like “What’s your favorite color?”) so people can type in whatever, but you also want to be helpful. Perhaps there are a handful of really popular answers. Wouldn’t it be nice if people could just select one? Not a <select>, but a hybrid between an input and a dropdown. Hold on though. Don’t make your own custom React element just yet.

That’s what <datalist> is for. I just used it successfully the other day so I figured I’d blog it because blogging is cool.

Here are the basics:

See the Pen
Basic datalist usage
by Chris Coyier (@chriscoyier)
on CodePen.

The use case I was dealing with needed:

  1. One <input type="text"> for a username
  2. One <input type="text"> for a “flag” (an aribtrary string representing a permission)

I probably wouldn’t do a <datalist> for every username in a database. I don’t think there is a limit, but this is sitting in your HTML, so I’d say it works best at maybe 100 options or less.

But for that second one, we only had maybe 3-4 unique flags we were dealing with at the time, so a datalist for those made perfect sense. You can type in whatever you want, but this UI helps you select the most common choices. So dang useful. Maybe this could be useful for something like a gender input, where there is a list of options you can choose, but it doesn’t enforce you actually choose one of them.

Even lesser known than the fact that <datalist> exists? The fact that it works for all sorts of inputs besides just text, like date, range, and even color.

The post Datalist is for suggesting values without enforcing values appeared first on CSS-Tricks.


, , , ,

Weekly news: Truncating muti-line text, calc() in custom property values, Contextual Alternates

In this week’s roundup, WebKit’s method for truncating multi-line text gets some love, a note on calculations using custom properties, and a new OpenType feature that prevents typographic logjams.

Truncating mutli-line text

The CSS -webkit-line-clamp property for truncating multi-line text is now widely supported (see my usage guide). If you use Autoprefixer, update it to the latest version (9.6.1). Previous versions would remove -webkit-box-orient: vertical, which caused this CSS feature to stop working.

Note that Autoprefixer doesn’t generate any prefixes for you in this case. You need to use the following four declarations exactly (all are required):

.line-clamp {   overflow: hidden;   display: -webkit-box;   -webkit-box-orient: vertical;   -webkit-line-clamp: 3; /* or any other integer */ }

(via Autoprefixer)

Calculations in CSS custom property values

In CSS, it is currently not possible to pre-calculate custom property values (spec). The computed value of a custom property is its specified value (with variables substituted); therefore, relative values in calc() expressions are not “absolutized” (e.g., em values are not computed to pixel values).

:root {   --large: calc(1em + 10px); }  blockquote {   font-size: var(--large); }

It may appear that the calculation in the above example is performed on the root element, specifically that the relative value 1em is computed and added to the absolute value 10px. Under default conditions (where 1em equals 16px on the root element), the computed value of --large would be 26px.

But that’s not what’s happening here. The computed value of --large is its specified value, calc(1em + 10px). This value is inherited and substituted into the value of the font-size property on the <blockquote> element.

blockquote {   /* the declaration after variable substitution */   font-size: calc(1em + 10px); }

Finally, the calculation is performed and the relative 1em value absolute-ized in the scope of the <blockquote> element — not the root element where the calc() expression is declared.

(via Tab Atkins Jr.)

Contextual Alternates

The “Contextual Alternates” OpenType feature ensures that characters don’t overlap or collide when ligatures are turned off. You can check if your font supports this feature on wakamaifondue.com and enable it (if necessary) via the CSS font-variant-ligatures: contextual declaration.

(via Jason Pamental)

Announcing daily news on webplatform.news

I have started posting daily news for web developers on webplatform.news. Visit every day!

The post Weekly news: Truncating muti-line text, calc() in custom property values, Contextual Alternates appeared first on CSS-Tricks.


, , , , , , , , , ,

DRY State Switching With CSS Variables: Fallbacks and Invalid Values

This is the second post in a two-part series that looks into the way CSS variables can be used to make the code for complex layouts and interactions less difficult to write and a lot easier to maintain. The first installment walks through various use cases where this technique applies. This post covers the use of fallbacks and invalid values to extend the technique to non-numeric values.

The strategy of using CSS Variables to drive the switching of layouts and interactions that we covered in the first post in this series comes with one major caveat: it only works with numeric values — lengths, percentages, angles, durations, frequencies, unit-less number values and so on. As a result, it can be really frustrating to know that you’re able to switch the computed values of more than ten properties with a single CSS variable, but then you need to explicitly switch the non-numeric values of properties like flex-direction or text-align from row to column or from left to right or the other way around.

One example would be the one below, where the text-align property depends on parity and the flex-direction depends on whether we are viewing the front end in the wide screen scenario or not.

Screenshot collage. On the left, we have the wide screen scenario, with four paragraphs as the four horizontal, offset based on parity slices of a disc. The slice numbering position is either to the right or left of the actual text content, depending on parity. The text alignment also depends on parity. In the middle, we have the normal screen case. The paragraphs are now full width rectangular elements. On the right, we have the narrow screen case. The paragraph numbering is always above the actual text content in this case.
Screenshot collage.

I complained about this and got a very interesting suggestion in return that makes use of CSS variable fallbacks and invalid values. It was interesting and gives us something new to work with, so let’s start with a short recap of what these are and go from there!

Fallback values

The fallback value of a CSS variable is the second and optional argument of the var() function. For example, let’s consider we have some .box elements whose background is set to a variable of --c:

.box { background: var(--c, #ccc) }

If we haven’t explicitly specified a value for the --c variable elsewhere, then the fallback value #ccc is used.

Now let’s say some of these boxes have a class of .special. Here, we can specify --c as being some kind of orange:

.special { --c: #f90 }

This way, the boxes with this .special class have an orange background, while the others use the light grey fallback.

See the Pen by thebabydino (@thebabydino) on CodePen.

There are a few things to note here.

First off, the fallback can be another CSS variable, which can have a CSS variable fallback itself and… we can fall down a really deep rabbit hole this way!

background: var(--c, var(--c0, var(--c1, var(--c2, var(--c3, var(--c4, #ccc))))))

Secondly, a comma separated list is a perfectly valid fallback value. In fact, everything specified after the first comma inside the var() function constitutes the fallback value, as seen in the example below:

background: linear-gradient(90deg, var(--stop-list, #ccc, #f90))

See the Pen by thebabydino (@thebabydino) on CodePen.

And last, but certainly not least, we can have different fallback values for the same variable used in different places, as illustrated by this example:

$ highlight: #f90;  a {   border: solid 2px var(--c, #{rgba($ highlight, 0)})   color: var(--c, #ccc);      &:hover, &:focus { --c: #{$ highlight} } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Invalid values

First off, I want to clarify what I mean by this. “Invalid values” is shorter and easier to remember, but what it really refers to any value that makes a declaration invalid at computed value time.

For example, consider the following piece of code:

--c: 1em; background: var(--c)

1em is a valid length value, but this is not a valid value for the background-color property, so here this property will take its initial value (which is transparent) instead.

Putting it all together

Let’s say we have a bunch of paragraphs where we change the lightness of the color value to switch between black and white based on parity (as explained in the previous post in this series):

p {   --i: 0;   /* for --i: 0 (odd), the lightness is 0*100% = 0% (black)    * for --i: 1 (even), the lightness is 1*100% = 100% (white)* /   color: hsl(0, 0%, calc(var(--i)*100%));    &:nth-child(2n) { --i: 1 } }

We also want the odd paragraphs to be right-aligned, while keeping the even ones left-aligned. In order to achieve this, we introduce a --parity variable which we don’t set explicitly in the general case — only for even items. What we do set in the general case is our previous variable, --i. We set it to the value of --parity with a fallback of 0:

p {   --i: var(--parity, 0);   color: hsl(0, 0%, calc(var(--i)*100%));    &:nth-child(2n) { --parity: 1 } }

So far, this achieves exactly the same as the previous version of our code. However, if we take advantage of the fact that, we can use different fallback values in different places for the same variable, then we can also set text-align to the value of --parity using a fallback of… right!

text-align: var(--parity, right)

In the general case, where we’re not setting --parity explicitly; text-align uses the fallback right, which is a valid value, so we have right alignment. For the even items however, we’re setting --parity explicitly to 1, which is not a valid value for text-align. That means text-align reverts to its initial value, which is left.

See the Pen by thebabydino (@thebabydino) on CodePen.

Now we have right alignment for the odd items and left alignment for the even items while still putting a single CSS variable to use!

Dissecting a more complex example

Let’s consider we want to get the result below:

Screenshot. Shows a bunch of numbered cards. Odd ones have the numbering on the left, while even ones have it on the right. Odd ones are right-aligned, while even ones are left-aligned. Odd ones are shifted a bit to the right and have a bit of a clockwise rotation, while even ones are shifted and rotated by the same amounts, but in the opposite directions. All have a grey to orange gradient background, but for the odd ones, this gradient goes from left to right, while for the even ones it goes from right to left.
Numbered cards where even cards have symmetrical styles with respect to odd cards.

We create these cards with a paragraph element <p> for each one. We switch their box-sizing to border-box, then give them a width, a max-width, a padding and a margin. We also change the default font.

See the Pen by thebabydino (@thebabydino) on CodePen.

We’ve also added a dummy outline just to see the boundaries of these elements.

Next, let’s add the numbering using CSS counters and a :before pseudo-element:

p {   /* same code as before */   counter-increment: c;      &:before { content: counter(c, decimal-leading-zero) } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now, we’ll give our paragraphs a flex layout and increase the size of the numbering:

p {   /* same code as before */   display: flex;   align-items: center;      &:before {     font-size: 2em;     content: counter(c, decimal-leading-zero);   } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now comes the interesting part!

We set a switch --i that changes value with the parity — it’s 0 for the odd items and 1 for the even ones.

p {   /* same code as before */   --i: 0;      &:nth-child(2n) { --i: 1 } }

Next, we want the numbering to be on the left for the odd items and on the right for the even ones. We achieve this via the order property. The initial value for this property is 0, for both the :before pseudo-element and the paragraph’s text content. If we set this order property to 1 for the numbering (the :before pseudo-element) of the even elements, then this moves the numbering after the content.

p {   /* same code as before */   --i: 0;      &:before {     /* same code as before */     /* we don't really need to set order explicitly as 0 is the initial value */     order: 0;   }      &:nth-child(2n) {     --i: 1;          &:before { order: 1 }   } }

You may notice that, in this case, the order value is the same as the switch --i value, so in order to simplify things, we set the order to the switch value.

p {   /* same code as before */   --i: 0;      &:before {     /* same code as before */     order: var(--i)   }      &:nth-child(2n) { --i: 1 } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now we want a bit of spacing (let’s say $ gap) in between the numbers and the paragraph text. This can be achieved with a lateral margin on the :before.

For the odd items, the item numbers are on the left, so we need a non-zero margin-right. For the even items, the item numbers are on the right, so we need a non-zero margin-left.

When the parity switch value is 0 for the odd items, the left margin is 0 = 0*$ gap, while the right margin is $ gap = 1*$ gap = (1 - 0)*$ gap.

Similarly for the even items, when the parity switch value is 1, the left margin is $ gap = 1*$ gap, while the right margin is 0 = 0*$ gap = (1 - 1)*$ gap.

The result in both cases is that margin-left is the parity switch value times the margin value ($ gap), while margin-right is 1 minus the parity switch value, all multiplied with the margin value.

$ gap: .75em;  p {   /* same code as before */   --i: 0;      &:before {     /* same code as before */     margin:        0                            /* top */       calc((1 - var(--i))*#{$ gap}) /* right */       0                            /* bottom */       calc(var(--i)*#{$ gap})       /* left */;   }      &:nth-child(2n) { --i: 1 } }

If we use the complementary value (1 - var(--i)) in more than one place, then it’s probably best to set it to another CSS variable --j.

$ gap: .75em;  p {   /* same code as before */   --i: 0;   --j: calc(1 - var(--i));      &:before {     /* same code as before */     margin:        0                      /* top */       calc(var(--j)*#{$ gap}) /* right */       0                      /* bottom */       calc(var(--i)*#{$ gap}) /* left */;   }      &:nth-child(2n) { --i: 1 } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we want to give these items a proper background. This is a grey to orange gradient, going from left to right (or along a 90deg angle) in the case of odd items (parity switch --i: 0) and from right to left (at a -90deg angle) in the case of even items (parity switch --i: 1).

This means the absolute value of the gradient angle is the same (90deg), only the sign is different — it’s +1 for the odd items (--i: 0) and -1 for the even items (--i: 1).

In order to switch the sign, we use the approach we covered in the first post:

/*  * for --i: 0, we have 1 - 2*0 = 1 - 0 = +1  * for --i: 1, we have 1 - 2*1 = 1 - 2 = -1  */ --s: calc(1 - 2*var(--i))

This way, our code becomes:

p {   /* same code as before */   --i: 0;   --s: calc(1 - 2*var(--i));   background: linear-gradient(calc(var(--s)*90deg), #ccc, #f90);      &:nth-child(2n) { --i: 1 } }

We can also remove the dummy outline since we don’t need it at this point:

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we do something similar for the transform property.

The odd items are translated a bit to the right (in the positive direction of the x axis) and rotated a bit in the clockwise (positive) direction, while the even items are translated a bit to the left (in the negative direction of the x axis) and rotated a bit in the other (negative) direction.

The translation and rotation amounts are the same; only the signs differ.

For the odd items, the transform chain is:

translate(10%) rotate(5deg)

While for the even items, we have:

translate(-10%) rotate(-5deg)

Using our sign --s variable, the unified code is:

p {   /* same code as before */   --i: 0;   --s: calc(1 - 2*var(--i));   transform: translate(calc(var(--s)*10%))               rotate(calc(var(--s)*5deg));      &:nth-child(2n) { --i: 1 } }

This is now starting to look like something!

See the Pen by thebabydino (@thebabydino) on CodePen.

The next step is to round the card corners. For the odd cards, we want the corners on the left side to be rounded to a radius of half the height. For the even items, we want the corners on the right side to be rounded to the same radius.

Given we don’t know the heights of our cards, we just use a ridiculously large value, say something like 50vh, which gets scaled down to fit due to the way border-radius works. In our case, this means scaled down to whichever is smaller between half the item height (since going vertically has both a top and bottom rounded corner on the same side) and the full item width (since going horizontally has one rounded corner; either on the left or on the right, but not on both the right and the left).

This means we want the corners on the left to have this radius ($ r: 50vh) for odd items (--i: 0) and the ones on the right to have the same radius for even items (--i: 1). As a result, we do something pretty similar to the numbering margin case:

$ r: 50vh;  p {   /* same code as before */   --i: 0;   --j: calc(1 - var(--i));   --r0: calc(var(--j)*#{$ r});   --r1: calc(var(--i)*#{$ r});   /* clockwise from the top left */   border-radius: var(--r0) /* top left */                  var(--r1) /* top right */                  var(--r1) /* bottom right */                  var(--r0) /* bottom left */;      &:nth-child(2n) { --i: 1 } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now comes the truly interesting part — text alignment! We want the text in the odd items to be aligned right, while the text in the even items is aligned left. The only problem is that text-align doesn’t take a number value so, no addition or multiplication tricks can help us here.

What can help is combining the use of fallback and invalid values for CSS variables. To do this, we introduce another parity variable --p and it’s this variable that we actually set to 1 for even items. Unlike --i before, we never set --p explicitly for the general case as we want different fallback values of this variable to be used for different properties.

As for --i, we set it to --p with a fallback value of 0. This fallback value of 0 is the value that actually gets used in the general case, since we never explicitly set --p there. For the even case, where we explicitly set --p to 1, --i becomes 1 as well.

At the same time, we set the text-align property to --p with a fallback value of right in the general case. In the even case, where we have --p explicitly set to 1, the text-align value becomes invalid (because we have set text-align to the value of --p and --p is now 1, which is not a valid value for text-align), so the text reverts to being aligned to the left.

p {   /* same code as before */   --i: var(--p, 0);   text-align: var(--p, right);      &:nth-child(2n) { --p: 1 } }

This gives us the result we’ve been after:

See the Pen by thebabydino (@thebabydino) on CodePen.

Handling responsiveness

While our cards example looks great on wider screens, the same can’t be said when shrink things down.

Screenshot collage. Since the width of the cards depends on the viewport width, the viewport may get too narrow to allow for displaying the numbering and the paragraph text side by side and the right one of the two overflows in this case.
The wide screen result (left) vs. the narrow screen result (right)

In order to fix this, we introduce two more custom properties, --wide and --k to switch between the wide and narrow cases. We set --k to --wide with a fallback value of 0 in the general case and then set --wide to 1 if the viewport width is anything 340px and up.

p {   /* same code as before */   --k: var(--wide, 0);      @media (min-width: 340px) { --wide: 1 } }

Since we only want our items to be transformed and have rounded corners in the wide case, we multiply the translation, rotation and radius values by --k (which is 0, unless the viewport is wide, which switches its value to 1).

p {   /* same code as before */   --k: var(--wide, 0);   --r0: calc(var(--k)*var(--j)*#{$ r});   --r1: calc(var(--k)*var(--i)*#{$ r});   border-radius: var(--r0) /* top left */                  var(--r1) /* top right */                  var(--r1) /* bottom right */                  var(--r0) /* bottom left */;   transform: translate(calc(var(--k)*var(--s)*10%))               rotate(calc(var(--k)*var(--s)*5deg));    @media (min-width: 340px) { --wide: 1 } }

This is slightly better, but our content still overflows in narrow viewports. We can fix this by only placing the numbering (the :before pseudo-element) on the left or right side only in the wide case then moving it above the card in the narrow case.

In order to do this, we multiply both its order and its lateral margin values by --k (which is 1 in the wide case and 0 otherwise).

We also set flex-direction to --wide with a fallback value of column.

This means the flex-direction value is column in the general case (since we haven’t set --wide explicitly elsewhere). However, if the viewport is wide (min-width: 340px), then our --wide variable gets set to 1. But 1 is an invalid value for flex-direction, so this property reverts back to its initial value of row.

p {   /* same code as before */   --k: var(--wide, 0);   flex-direction: var(--wide, column);      &:before {     /* same code as before */     order: calc(var(--k)*var(--i));     margin:        0                               /* top */       calc(var(--k)*var(--j)*#{$ gap}) /* right */       0                               /* bottom */       calc(var(--k)*var(--i)*#{$ gap}) /* left */;   }      @media (min-width: 340px) { --wide: 1 } }

Coupled with setting a min-width of 160px on the body, we’ve now eliminated the overflow issue:

Responsive cards, no overflow (live demo).

One more thing we can do is tweak the font-size so that it also depends on --k:

p {   /* same code as before */   --k: var(--wide, 0);   font: 900 calc(var(--k)*.5em + .75em) cursive;    @media (min-width: 340px) { --wide: 1 } }

And that’s it, our demo is now nicely responsive!

Responsive cards, font smaller for narrow screens and with no overflow (live demo).

A few more quick examples!

Let’s look at a few more demos that use the same technique, but quickly without building them from scratch. We’ll merely go through the basic ideas behind them.

Disc slices

Sliced disc (live demo).

Just like the cards example we completed together, we can use a :before pseudo-element for the numbering and a flex layout on the paragraphs. The sliced disc effect is achieved using clip-path.

The paragraph elements themselves — the horizontal offsets, the position and intensity of the radial-gradient() creating the shadow effect, the direction of the linear-gradient() and the saturation of its stops, the color and the text alignment — all depend on the --parity variable.

p {   /* other styles not relevant here */   --p: var(--parity, 1);   --q: calc(1 - var(--p));   --s: calc(1 - 2*var(--p)); /* sign depending on parity */   transform: translate((calc(var(--i)*var(--s)*#{-$ x})));   background:      radial-gradient(at calc(var(--q)*100%) 0,        rgba(0, 0, 0, calc(.5 + var(--p)*.5)), transparent 63%)        calc(var(--q)*100%) 0/ 65% 65% no-repeat,      linear-gradient(calc(var(--s)*-90deg),        hsl(23, calc(var(--q)*98%), calc(27% + var(--q)*20%)),        hsl(44, calc(var(--q)*92%), 52%));   color: HSL(0, 0%, calc(var(--p)*100%));   text-align: var(--parity, right); 	   &:nth-child(odd) { --parity: 0 } }

For the numbering (the :before pseudo-elements of the paragraphs), we have that both the margin and the order depend on the --parity in the exact same way as the cards example.

If the viewport width is smaller than the disc diameter $ d plus twice the horizontal slice offset in absolute value $ x, then we’re not in the --wide case anymore. This affects the width, padding and margin of our paragraphs, as well as their horizontal offset and their shape (because we don’t clip them to get the sliced disc effect at that point).

body {   /* other styles not relevant here */   --i: var(--wide, 1);   --j: calc(1 - var(--i)); 	   @media (max-width: $ d + 2*$ x) { --wide: 0 } }  p {   /* other styles not relevant here */   margin: calc(var(--j)*.25em) 0;   padding:      calc(var(--i)*#{.5*$ r}/var(--n) + var(--j)*5vw) /* vertical */     calc(var(--i)*#{.5*$ r} + var(--j)*2vw) /* horizontal */;   width: calc(var(--i)*#{$ d} /* wide */ +                var(--j)*100% /* not wide */);   transform: translate((calc(var(--i)*var(--s)*#{-$ x})));   clip-path:      var(--wide,                 /* fallback, used in the wide case only */       circle($ r at 50% calc((.5*var(--n) - var(--idx))*#{$ d}/var(--n)))); }

We’re in the narrow case below 270px and have a flex-direction of column on our paragraphs. We also zero out both the lateral margins and the order for the numbering.

body {   /* other styles not relevant here */   --k: calc(1 - var(--narr, 1)); 	   @media (min-width: 270px) { --narr: 0 } }  p {   /* other styles not relevant here */   flex-direction: var(--narr, column);    &:before {     /* other styles not relevant here */     margin:        0                             /* top */       calc(var(--k)*var(--q)*.25em) /* right */       0                             /* bottom */       calc(var(--k)*var(--p)*.25em) /* left */;     order: calc(var(--k)*var(--p));   } }

Four-step infographic

Screenshot collage. On the left, there's the wide screen scenario. In the middle, there's the normal screen scenario. On the right, there's the narrow screen scenario.
A four-step infographic (live demo).

This works pretty much the same as the previous two examples. We have a flex layout on our paragraphs using a column direction in the narrow case. We also have a smaller font-size in that same case:

body {   /* other styles not relevant here */   --k: var(--narr, 1);      @media (min-width: 400px) { --narr: 0 } }  p {   /* other styles not relevant here */   flex-direction: var(--narr, column);   font-size: calc((1.25 - .375*var(--k))*1em); }

The parity determines each paragraph’s text alignment, which lateral border gets a non-zero value, and the position and direction of the border gradient. Both the parity and whether we’re in the wide screen case or not determine the lateral margins and paddings.

body {   /* other styles not relevant here */   --i: var(--wide, 1);   --j: calc(1 - var(--i));      @media (max-width: $ bar-w + .5*$ bar-h) { --wide: 0 } }  p {   /* other styles not relevant here */   margin:      .5em                                 /* top */     calc(var(--i)*var(--p)*#{.5*$ bar-h}) /* right */     0                                    /* bottom */     calc(var(--i)*var(--q)*#{.5*$ bar-h}) /* left */;   border-width:      0                        /* top */     calc(var(--q)*#{$ bar-b}) /* right */     0                        /* bottom */     calc(var(--p)*#{$ bar-b}) /* left */;   padding:      $ bar-p                                         /* top */     calc((var(--j) + var(--i)*var(--q))*#{$ bar-p}) /* right */     $ bar-p                                         /* bottom */     calc((var(--j) + var(--i)*var(--p))*#{$ bar-p}) /* left */;   background:      linear-gradient(#fcfcfc, gainsboro) padding-box,      linear-gradient(calc(var(--s)*90deg), var(--c0), var(--c1))        calc(var(--q)*100%) /* background-position */ /        #{$ bar-b} 100% /* background-size */;   text-align: var(--parity, right); }

The icon is created using the :before pseudo-element, and its order depends on the parity, but only if we’re not in the narrow screen scenario — in which case it’s always before the actual text content of the paragraph. Its lateral margin depends both on the parity and whether we are in the wide screen case or not. The big-valued component that positions it half out of its parent paragraph is only present in the wide screen case. The font-size also depends on whether we’re in the narrow screen case or not (and this influences its em dimensions and padding).

order: calc((1 - var(--k))*var(--p)); margin:    0                                                          /* top */   calc(var(--i)*var(--p)*#{-.5*$ ico-d} + var(--q)*#{$ bar-p}) /* right */   0                                                          /* bottom */   calc(var(--i)*var(--q)*#{-.5*$ ico-d} + var(--p)*#{$ bar-p}) /* left */; font-size: calc(#{$ ico-s}/(1 + var(--k)));

The ring is created using an absolutely positioned :after pseudo-element (and its placement depends on parity), but only for the wide screen case.

content: var(--wide, '');

The two-dimension case

Screenshot collage. On the left, we have the wide screen scenario. Each article is laid out as a 2x2 grid, with the numbering occupying an entire column, either on the right for odd items or on the left for even items. The heading and the actual text occupy the other column. In the middle, we have the normal screen case. Here, we also have a 2x2 grid, but the numbering occupies only the top row on the same column as before, while the actual text content now spans both columns on the second row. On the right, we have the narrow screen case. In this case, we don't have a grid anymore, the numbering, the heading and the actual text are one under the other for each article.
Screenshot collage (live demo, no Edge support due to CSS variable and calc() bugs).

Here we have a bunch of article elements, each containing a heading. Let’s check out the most interesting aspects of how this responsive layout works!

On each article, we have a two-dimensional layout (grid) — but only if we’re not in the narrow screen scenario (--narr: 1), in which case we fall back on the normal document flow with the numbering created using a :before pseudo-element, followed by the heading, followed by the actual text. In this situation, we also add vertical padding on the heading since we don’t have the grid gaps anymore and we don’t want things to get too crammed.

html {   --k: var(--narr, 0); 	   @media (max-width: 250px) { --narr: 1 } }  article {   /* other styles irrelevant here */   display: var(--narr, grid); }  h3 {   /* other styles irrelevant here */   padding: calc(var(--k)*#{$ hd3-p-narr}) 0; }

For the grid, we create two columns of widths depending both on parity and on whether we’re in the wide screen scenario. We make the numbering (the :before pseudo-element) span two rows in the wide screen case, either on the second column or the first, depending on the parity. If we’re not in the wide screen case, then the paragraph spans both columns on the second row.

We set the grid-auto-flow to column dense in the wide screen scenario, letting it revert to the initial value of row otherwise. Since our article elements are wider than the combined widths of the columns and the column gap between them, we use place-content to position the actual grid columns inside at the right or left end depending on parity.

Finally, we place the heading at the end or start of the column, depending on parity, and we as well as the paragraph’s text alignment if we’re in the wide screen scenario.

$ col-1-wide: calc(var(--q)*#{$ col-a-wide} + var(--p)*#{$ col-b-wide}); $ col-2-wide: calc(var(--p)*#{$ col-a-wide} + var(--q)*#{$ col-b-wide});  $ col-1-norm: calc(var(--q)*#{$ col-a-norm} + var(--p)*#{$ col-b-norm}); $ col-2-norm: calc(var(--p)*#{$ col-a-norm} + var(--q)*#{$ col-b-norm});  $ col-1: calc(var(--i)*#{$ col-1-wide} + var(--j)*#{$ col-1-norm}); $ col-2: calc(var(--i)*#{$ col-2-wide} + var(--j)*#{$ col-2-norm});  html {   --i: var(--wide, 1);   --j: calc(1 - var(--i)); 	   @media (max-width: $ art-w-wide) { --wide: 0 } }  article {   /* other styles irrelevant here */   --p: var(--parity, 1);   --q: calc(1 - var(--p));   grid-template-columns: #{$ col-1} #{$ col-2};   grid-auto-flow: var(--wide, dense column);   place-content: var(--parity, center end);      &:before {     /* other styles irrelevant here */     grid-row: 1/ span calc(1 + var(--i));     grid-column: calc(1 + var(--p))/ span 1;   }      &:nth-child(odd) { --parity: 0 } }  h3 {   /* other styles irrelevant here */   justify-self: var(--parity, self-end); }  p {   grid-column-end: span calc(1 + var(--j));   text-align: var(--wide, var(--parity, right)); }

We also have numerical values such as grid gaps, border radii, paddings, font-sizes, gradient directions, rotation and translation directions depending on the parity and/or whether we’re in the wide screen scenario or not.

Even more examples!

If you want more of this, I’ve created an entire collection of similar responsive demos for you to enjoy!

Screenshot of collection page on CodePen, showing the six most recent demos added.
Collection of responsive demos.

The post DRY State Switching With CSS Variables: Fallbacks and Invalid Values appeared first on CSS-Tricks.


, , , , ,