Tag: Guide

A Complete Guide to Custom Properties

A custom property is most commonly thought of as a variable in CSS.

.card {   --spacing: 1.2rem;   padding: var(--spacing);   margin-bottom: var(--spacing); }

Above, --spacing is the custom property with 1.2rem as the value and var(--spacing) is the variable in use.

Perhaps the most valuable reason to use them: not repeating yourself (DRY code). In the example above, I can change the value 1.2rem in one place and have it affect two things. This brings something programming languages do to CSS.

There is a good bit to know about custom properties, so let’s get into it.

Why care about CSS Custom Properties?

  1. They help DRY up your CSS. That is “Don’t Repeat Yourself.” Custom properties can make code easier to maintain because you can update one value and have it reflected in multiple places. Careful though, overdoing abstraction can make have the opposite effect and make code less understandable.
  2. They are particularly helpful for things like creating color themes on a website.
  3. They unlock interesting possibilities in CSS.
  4. The fact that they can be updated in JavaScript opens up even more interesting doors.

Naming custom properties

Custom properties must be within a selector and start with two dashes (--):

/* Nope, not within a selector */ --foo: 1;  body {   /* No, 0 or 1 dash won't work */   foo: 1;   -foo: 1;     /* Yep! */   --foo: 1;    /* OK, but they're different properties */   --FOO: 1;   --Foo: 1;      /* Totally fine */   --mainColor: red;   --main-color: red;    /* Special characters are a no */   --color@home: red;   --black&blue: black;   --black^2: black; }

Best to stick with letters, numbers, and dashes while making sure the custom property is defined inside of a valid selector.

Properties as properties

You can set the value of a custom property with another custom property:

html {   --red: #a24e34;   --green: #01f3e6;   --yellow: #f0e765;    --error: var(--red);   --errorBorder: 1px dashed var(--red);   --ok: var(--green);   --warning: var(--yellow); }

Some people like doing it this way because it allows the name of a custom property to be descriptive and then used in another property with a more functional name, again helping keep things DRY. It can even help make the functional names more readable and understandable.

Valid values for custom properties

Custom properties are surprisingly tolerant when it comes to the values they accept.

Here are some basic examples that you’d expect to work, and do.

body {   --brand-color: #990000;   --transparent-black: rgba(0, 0, 0, 0.5);      --spacing: 0.66rem;   --max-reading-length: 70ch;   --brandAngle: 22deg;    --visibility: hidden;   --my-name: "Chris Coyier"; }

See that? They can be hex values, color functions, units of all kinds, and even strings of text.

But custom properties don’t have to be complete values like that. Let’s look at how useful it can be to break up valid CSS values into parts we can shove into custom properties.

Breaking up values

You can use custom properties to break up multi-part values.

Let’s imagine you’re using a color function, say rgba(). Each color channel value in there can be its own custom property. That opens up a ton of possibilities, like changing the alpha value for a specific use case, or perhaps creating color themes.

Splitting colors

Take HSL color, for example. We can split it up into parts, then very easily adjust the parts where we want. Maybe we’re working with the background color of a button. We can update specific parts of its HSL makeup when the button is hovered, in focus, or disabled, without declaring background on any of those states at all.

button {   --h: 100;   --s: 50%;   --l: 50%;   --a: 1;    background: hsl(var(--h) var(--s) var(--l) / var(--a)); } button:hover { /* Change the lightness on hover */   --l: 75%; } button:focus { /* Change the saturation on focus */   --s: 75%; } button[disabled] {  /* Make look disabled */   --s: 0%;   --a: 0.5; }

By breaking apart values like that, we can control parts of them in a way we never could before. Just look at how we didn’t need to declare all of the HSL arguments to style the hover, focus and disabled state of a button. We simply overrode specific HSL values when we needed to. Pretty cool stuff!

Shadows

box-shadow doesn’t have a shorthand property for controlling the shadow’s spread on its own. But we could break out the box-shadow spread value and control it as a custom property (demo).

button {   --spread: 5px;   box-shadow: 0 0 20px var(--spread) black; } button:hover {   --spread: 10px; }

Gradients

There is no such thing as a background-gradient-angle (or the like) shorthand for gradients. With custom properties, we can change just change that part as if there was such a thing.

body {   --angle: 180deg;   background: linear-gradient(var(--angle), red, blue); } body.sideways {   --angle: 90deg; }

Comma-separated values (like backgrounds)

Any property that supports multiple comma-separated values might be a good candidate for splitting values too, since there is no such thing as targeting just one value of a comma-separated list and changing it alone.

/* Lots of backgrounds! */ background-image:   url(./img/angles-top-left.svg),   url(./img/angles-top-right.svg),   url(./img/angles-bottom-right.svg),   url(./img/angles-bottom-left.svg),   url(./img/bonus-background.svg);

Say you wanted to remove just one of many multiple backgrounds at a media query. You could do that with custom properties like this, making it a trivial task to swap or override backgrounds.

body {   --bg1: url(./img/angles-top-left.svg);   --bg2: url(./img/angles-top-right.svg);   --bg3: url(./img/angles-bottom-right.svg);   --bg4: url(./img/angles-bottom-left.svg);   --bg5: url(./img/bonus-background.svg);      background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4); } @media (min-width: 1500px) {   body {     background-image: var(--bg1), var(--bg2), var(--bg3), var(--bg4), var(--bg5);   } }

Grids

We’re on a roll here, so we might as well do a few more examples. Like, hey, we can take the grid-template-columns property and abstract its values into custom properties to make a super flexible grid system:

.grid {   display: grid;   --edge: 10px;   grid-template-columns: var(--edge) 1fr var(--edge); } @media (min-width: 1000px) {   .grid {      --edge: 15%;    } }

Transforms

CSS will soon get individual transforms but we can get it sooner with custom properties. The idea is to apply all the transforms an element might get up front, then control them individually as needed:

button {   transform: var(--scale, scale(1)) var(--translate, translate(0)); } button:active {   --translate: translate(0, 2px); } button:hover {   --scale: scale(0.9); }

Concatenation of unit types

There are times when combining parts of values doesn’t work quite how you might hope. For example, you can’t make 24px by smashing 24 and px together. It can be done though, by multiplying the raw number by a number value with a unit.

body {   --value: 24;   --unit: px;      /* Nope */   font-size: var(--value) + var(--unit);      /* Yep */   font-size: calc(var(--value) * 1px);    /* Yep */   --pixel_converter: 1px;   font-size: calc(var(--value) * var(--pixel_converter)); }

Using the cascade

The fact that custom properties use the cascade is one of the most useful things about them.

You’ve already seen it in action in many of the examples we’ve covered, but let’s put a point on it. Say we have a custom property set pretty “high up” (on the body), and then set again on a specific class. We use it on a specific component.

body {   --background: white; } .sidebar {   --background: gray; } .module {   background: var(--background); }

Then say we’ve got practical HTML like this:

<body> <!-- --background: white -->    <main>     <div class="module">       I will have a white background.     </div>   <main>    <aside class="sidebar"> <!-- --background: gray -->     <div class="module">       I will have a gray background.     </div>   </aside>  </body>

Three CSS rulesets, one for a body, sidebar and module. the background custom property is defined as white on body and gray on sidebar. The module calls the custom property and shows an orange arrow pointing to the custom property defined in the sidebar since it is the nearest ancestor.
For the second module, .sidebar is a closer ancestor than body, thus --background resolves to gray there, but white in other places.

The “module” in the sidebar has a gray background because custom properties (like many other CSS properties) inherit through the HTML structure. Each module takes the --background value from the nearest “ancestor” where it’s been defined in CSS.

So, we have one CSS declaration but it’s doing different things in different contexts, thanks to the cascade. That’s just cool.

This plays out in other ways:

button {   --foo: Default; } button:hover {   --foo: I win, when hovered;   /* This is a more specific selector, so re-setting       custom properties here will override those in `button` */ }

Media queries don’t change specificity, but they often come later (or lower) in the CSS file than where the original selector sets a value, which also means a custom property will be overridden inside the media query:

body {   --size: 16px;   font-size: var(--size); } @media (max-width: 600px) {   body {     --size: 14px;   }  }

Media queries aren’t only for screen sizes. They can be used for things like accessibility preferences. For example, dark mode:

body {   --bg-color: white;    --text-color: black;    background-color: var(--bg-color);   color: var(--text-color); }  /* If the user's preferred color scheme is dark */ @media screen and (prefers-color-scheme: dark) {   body {     --bg-color: black;     --text-color: white;   } }

The :root thing

You’ll often see custom properties being set “at the root.” Here’s what that means:

:root {   --color: red; }  /* ...is largely the same as writing: */ html {   --color: red; }  /* ...except :root has higher specificity, so remember that! */

There is no particularly compelling reason to define custom properties like that. It’s just a way of setting custom properties as high up as they can go. If you like that, that’s totally fine. I find it somehow more normal-feeling to apply them to the html or body selectors when setting properties I intend to make available globally, or everywhere.

There is also no reason you need to set variables at this broad of a scope. It can be just as useful, and perhaps more readable and understandable, to set them right at the level you are going to use them (or fairly close in the DOM tree).

.module {   --module-spacing: 1rem;   --module-border-width: 2px;    border: var(--module-border-width) solid black; }  .module + .module {   margin-top: var(--module-spacing); }

Note that setting a custom property on the module itself means that property will no longer inherit from an ancestor (unless we set the value to inherit). Like other inherited properties, there are sometimes reasons to specify them in place (at the global level), and other times we want to inherit them from context (at the component level). Both are useful. What’s cool about custom properties is that we can define them in one place, inherit them behind the scenes and apply them somewhere completely different. We take control of the cascade!

Combining with !important

You can make an !important modifier within or outside of a variable.

.override-red {   /* this works */   --color: red !important;     color: var(--color);    /* this works, too */   --border: red;   border: 1px solid var(--border) !important; }

Applying !important to the --color variable, makes it difficult to override the value of the --color variable, but we can still ignore it by changing the color property. In the second example, our --border variable remains low-specificity (easy to override), but it’s hard to change how that value will be applied to the border itself.

Custom property fallbacks

The var() function is what allows for fallback values in custom properties.

Here we’re setting a scale() transform function to a custom property, but there is a comma-separated second value of 1.2. That 1.2 value will be used if --scale is not set.

.bigger {   transform: scale(var(--scale, 1.2)); }

After the first comma, any additional commas are part of the fallback value. That allows us to create fallbacks with comma-separated values inside them. For example, we can have one variable fall back to an entire stack of fonts:

html {   font-family: var(--fonts, Helvetica, Arial, sans-serif); }

We can also provide a series of variable fallbacks (as many as we want), but we have to nest them for that to work:

.bigger {   transform: scale(var(--scale, var(--second-fallback, 1.2)); }

If --scale is undefined, we try the --second-fallback. If that is also undefined, we finally fall back to 1.2.

Using calc() and custom properties

Even more power of custom properties is unlocked when we combine them with math!

This kind of thing is common:

main {   --spacing: 2rem; }  .module {   padding: var(--spacing); }  .module.tight {   /* divide the amount of spacing in half */   padding: calc(var(--spacing) / 2));  }

We could also use that to calculate the hue of a complementary color:

html {   --brand-hue: 320deg;   --brand-color: hsl(var(--brand-hue), 50%, 50%);   --complement: hsl(calc(var(--brand-hue) + 180deg), 50%, 50%); }

calc() can even be used with multiple custom properties:

.slider {   width: calc(var(--number-of-boxes) * var(--width-of-box)); }

Deferring the calc()

It might look weird to see calculous-like math without a calc():

body {   /* Valid, but the math isn't actually performed just yet ... */   --font-size: var(--base-font-size) * var(--modifier);    /* ... so this isn't going to work */   font-size: var(--font-size); }

The trick is that as long as you eventually put it in a calc() function, it works fine:

body {   --base-font-size: 16px;   --modifier: 2;   --font-size: var(--base-font-size) * var(--modifier);    /* The calc() is "deferred" down to here, which works */   font-size: calc(var(--font-size)); }

This might be useful if you’re doing quite a bit of math on your variables, and the calc() wrapper becomes distracting or noisy in the code.

@property

The @property “at-rule” in CSS allows you to declare the type of a custom property, as well its as initial value and whether it inherits or not.

It’s sort of like you’re creating an actual CSS property and have the ability to define what it’s called, it’s syntax, how it interacts with the cascade, and its initial value.

@property --x {   syntax: '<number>';   inherits: false;   initial-value: 42; }
Valid Types
  • length
  • number
  • percentage
  • length-percentage
  • color
  • image
  • url
  • integer
  • angle
  • time
  • resolution
  • transform-list
  • transform-function
  • custom-ident (a custom identifier string)

This means that the browser knows what kind of value it is dealing with, rather than assuming everything is a string. That means you can animate things in ways you couldn’t otherwise.

For example, say you have a star-shaped icon that you want to spin around with @keyframes and rotate with a transform. So you do this:

.star {   --r: 0deg;   transform: rotate(var(--r));   animation: spin 1s linear infinite; }  @keyframes spin {   100% {     --r: 360deg;   } }

That actually won’t work, as the browser doesn’t know that 0deg and 360deg are valid angle values. You have to define them as an <angle> type with @property for that to work.

@property --angle {   syntax: '<angle>';   initial-value: 0deg;   inherits: false; }  .star {   --r: 0deg;   transform: rotate(var(--r));   animation: spin 1s linear infinite; }  @keyframes spin {   100% {     --r: 360deg;   } }
Demo

Commas in values

This can be a smidge confusing. Maybe not so much this:

html {   --list: 1, 2, 3; }

But below, you’ll need a sharp eye to realize the fallback value is actually 1.2, 2. The first comma separates the fallback, but all the rest is part of the value.

html {   transform: scale(var(--scale, 1.2, 2)); }

Learn more about fallbacks above ⮑

Advanced usage

The Raven is a technique that emulates container queries using math and custom properties. Be prepared, this goes from 0-100 in complexity right out of the gate!

Demo

Resize this demo to see a grid of inline-block elements change number of columns from 4 to 3 to 1.

Here’s a few more favorite examples that show off advanced usage of custom properties:

The initial and whitespace trick

Think of @media queries and how when one thing changes (e.g. the width of the page) you can control multiple things. That’s kind of the idea with this trick. You change one custom property and control multiple things.

The trick is that the value of initial for a custom property will trigger a fallback, while an empty whitespace value will not. For the sake of explanation, it let’s define two globally-scoped custom properties, ON and OFF:

:root {   --ON: initial;   --OFF: ; }

Say we have a “dark” variation class which sets a number of different properties. The default is --OFF, but can be flipped to --ON whenever:

.module {   --dark: var(--OFF); }  .dark { /* could be a media query or whatever */   --dark: var(--ON); }

Now you can use --dark to conditinally set values that apply only when you’ve flipped --dark to --ON. Demo:

Lea Verou has a great writeup that covers all of this.

Inline styles

It’s totally legit to set a custom property in HTML with an inline style.

<div style="--color: red;"></div>

That will, like any inline style, have a very high level of specificity.

This can be super useful for when the HTML might have access to some useful styling information that would be too weird/difficult to put into a static CSS file. A good example of that is maintaining the aspect ratio of an element:

<div style="--aspect-ratio: 16 / 9;"></div>

Now I can set up some CSS to make a box of that exact size wherever I need to. The full writeup on that is here, but here’s CSS that uses trickery like the ol’ padded box applied to a pseudo element which pushes the box to the desired size:

[style*="--aspect-ratio"] > :first-child {   width: 100%; } [style*="--aspect-ratio"] > img {     height: auto; }  @supports (--custom: property) {   [style*="--aspect-ratio"] {     position: relative;   }   [style*="--aspect-ratio"]::before {     content: "";     display: block;     padding-bottom: calc(100% / (var(--aspect-ratio)));   }     [style*="--aspect-ratio"] > :first-child {     position: absolute;     top: 0;     left: 0;     height: 100%;   }   }

But hey, these days, we have a native aspect-ratio property in CSS, so setting that in the inline style might make more sense going forward.

<div style="aspect-ratio: 16 / 9;"></div>

Hovers and pseudos

There is no way to apply a :hover style (or other pseudo classes/elements) with inline styles. That is, unless we get tricky with custom properties. Say we want custom hover colors on some boxes — we can pass that information in as a custom property:

<div style="--hover-color: red;"><div> <div style="--hover-color: blue;"><div> <div style="--hover-color: yellow;"><div>

Then use it in CSS which, of course, can style a link’s hover state:

div:hover {   background-color: var(--hover-color); }  /* And use in other pseudos! */ div:hover::after {   content: "I am " attr(style);   border-color: var(--hover-color); }

Custom properties and JavaScript

JavaScript can set the value of a custom property.

element.style.setProperty('--x', value);

Here’s an example of a red square that is positioned with custom properties, and JavaScript updates those custom property values with the mouse position:

Typically you think of JavaScript passing values to CSS to use, which is probably 99% of usage here, but note that you can pass things from CSS to JavaScript as well. As we’ve seen, the value of a custom property can be fairly permissive. That means you could pass it a logical statement. For example:

html {   --logic: if (x > 5) document.body.style.background = "blue"; }

Then grab that value and execute it in JavaScript:

const x = 10;  const logic = getComputedStyle(document.documentElement).getPropertyValue(   "--logic" );  eval(logic);

Custom properties are different than preprocessor variables

Say you’re already using Sass, Less, or Stylus. All those CSS preprocessors offer variables and it’s one of the main reasons to have them as part of your build process.

// Variable usage in Sass (SCSS) $ brandColor: red;  .marketing {   color: $ brandColor; }

So, do you even need to bother with native CSS custom properties then? Yes, you should. Here’s why in a nutshell:

  • Native CSS custom properties are more powerful then preprocessor variables. Their integration with the cascade in the DOM is something that preprocessor variables will never be able to do.
  • Native CSS custom properties are dynamic. When they change (perhaps via JavaScript, or with a media query), the browser repaints what it needs to. Preprocessor variables resolve to a value when they’re compiled and stay at that value.
  • Going with a native feature is good for the longevity of your code. You don’t need to preprocess native CSS.

I cover this in much more detail in the article “What is the difference between CSS variables and preprocessor variables?”

To be totally fair, there are little things that preprocessor variables can do that are hard or impossible with custom properties. Say you wanted to strip the units off a value for example. You can do that in Sass but you’ll have a much harder time with custom properties in CSS alone.

Can you preprocess custom properties?

Kinda. You can do this, with Sass just to pick one popular preprocessor:

$ brandColor: red; body {   --brandColor: $ brandColor; }

All that’s doing is moving a Sass variable to a custom property. That could be useful sometimes, but not terribly. Sass will just make --brandColor: red; there, not process the custom property away.

If a browser doesn’t support custom properties, that’s that. You can’t force a browser to do what custom properties do by CSS syntax transformations alone. There might be some kind of JavaScript polyfill that parses your CSS and replicates it, but I really don’t suggest that.

The PostCSS Custom Properties plugin, though, does do CSS syntax transforms to help. What it does is figure out the value to the best of it’s ability, and outputs that along with the custom property. So like:

:root {   --brandColor: red; } body {   color: var(--brandColor); }

Will output like this:

:root {   --brandColor: red; } body {   color: red;   color: var(--brandColor); }

That means you get a value that hopefully doesn’t seem broken in browsers that lack custom property support, but does not support any of the fancy things you can do with custom properties and will not even attempt to try. I’m a bit dubious about how useful that is, but I think this is about the best you can do and I like the spirit of attempting to not break things in older browsers or newer browsers.

Availiability

Another thing that is worth noting about the difference between is that with a CSS preprocessor, the variables are available only as you’re processing. Something like $ brandColor is meaningless in your HTML or JavaScript. But when you have custom properties in use, you can set inline styles that use those custom properties and they will work. Or you can use JavaScript to figure out their current values (in context), if needed.

Aside from some somewhat esoteric features of preprocessor variables (e.g. some math possibilities), custom properties are more capable and useful.

Custom properties and Web Components (Shadow DOM)

One of the most common and practical ways to style of Web Components (e.g. a <custom-component> with shadow DOM) is by using custom properties as styling hooks.

The main point of the shadow DOM is that it doesn’t “leak” styles in or out of it, offering style isolation in a way that nothing else offers, short of an <iframe>. Styles do still cascade their way inside, I just can’t select my way inside. This means custom properties will slide right in there.

Here’s an example:

Another common occurrence of the shadow DOM is with SVG and the <use> element.

Video: “CSS Custom Properties Penetrate the Shadow DOM”

Browser support

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

Chrome Firefox IE Edge Safari
49 31 No 16 10

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
90 87 90 10.0-10.2

You can preprocess for deeper browser support, with heavy limitations.

@supports

If you would like to write conditional CSS for when a browser supports custom properties or not:

@supports (--custom: property) {   /* Isolated CSS for browsers that DOES support custom properties, assuming it DOES support @supports */ }  @supports not (--custom: property) {   /* Isolated CSS for browsers that DON'T support custom properties, assuming it DOES support @supports */ }

Credit

Thanks to Miriam Suzanne for co-authoring this with me!


The post A Complete Guide to Custom Properties appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,

The Almost-Complete Guide to Cumulative Layout Shift

Here’s Jess B. Peck writing all about Google’s Core Web Vitals:

Let’s step back one. CLS is when you’re about to click on a link, and the whole page shifts and you click on a different link instead. It’s when you’re halfway through a blogpost and an ad loads and you lose your place. It is when… the layout shifts. At least, that’s what it’s trying to measure– both those shifts, how often they happen, and the irritation that causes the user.

I didn’t quite understand just how complex Cumulative Layout Shift is before reading Jess’s piece. As Jess explains:

CLS is a measure for a robot to approximate the user perception of instability. This means we’re getting a unit of change over time. It’s a three dimensional equation, and there are tons of things that can affect it. […] The idea is more to alert devs to a problem area, rather than be a perfect measurement of how annoying a page is.

I had this problem on, of all places, Google dot com. I kept tapping an element just as it appeared on screen and this sent me to the wrong page.

Jess notes that these metrics are sometimes more of an art than a science, and so we shouldn’t be obsessed with making sure that just these Core Web Vital metrics are okay. Chris mentioned a while ago that he worries folks might begin to game these metrics for improving their SEO:

This feels like the start of a weird new era of web performance where the metrics of web performance have shifted to user-centric measurements, but people are implementing tricky strategies to game those numbers with methods that, if anything, slightly harm user experience.

Harry Roberts mentioned something similar:

I feel like this is our responsibility as web developers, to explain that what we want to do here is reduce user misery on our websites. That’s not to say it’s easy, though, and there’s certainly not much we can do to avoid the shady folks who’ll game these metrics only to improve SEO.

As Jeremy wrote just the other day:

The map is not the territory. The numbers are a proxy for user experience, but it’s notoriously difficult to measure intangible ideas like pain and frustration.

Direct Link to ArticlePermalink


The post The Almost-Complete Guide to Cumulative Layout Shift appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

An Interactive Guide to CSS Transitions

A wonderful post by Josh that both introduces CSS transitions and covers the nuances for using them effectively. I like the advice about transitioning the position of an element, leaving the original space it occupied alone so it doesn’t result in what he calls “doom flicker.” Six hundred and fifty years ago I created CSS Jitter Man to attempt to explain that idea.

The interactive stuff is really neat and helps explain the concepts. I’m a little jealous that Josh writes in MDX — meaning he’s got Markdown and JSX at his disposal. That means these demos can be little one-off React components. Here’s a thread that Josh did showing off how valuable that can be.

Direct Link to ArticlePermalink


The post An Interactive Guide to CSS Transitions appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

A Complete Guide to CSS Gradients

The background-size property in CSS is one of the most useful — and most complex — of the background properties. There are many variations and different syntaxes you can use for this property, all of which have different use cases. Here’s a basic example:

html {   background: url(greatimage.jpg);   background-size: 300px 100px;  }

That’s an example of the two-value syntax for background size. There are four different syntaxes you can use with this property: the keyword syntax, the one-value syntax, the two-value syntax, and the multiple background syntax.

Keywords

In addition to the default value (auto), there are two keywords you can use with background-size: cover and contain

The difference

cover tells the browser to make sure the image always covers the entire container, even if it has to stretch the image or cut a little bit off one of the edges. contain, on the other hand, says to always show the whole image, even if that leaves a little space to the sides or bottom.

The default keyword — auto — tells the browser to automatically calculate the size based on the actual size of the image and the aspect ratio.

One Value

If you only provide one value (e.g. background-size: 400px) it counts for the width, and the height is set to auto. You can use any CSS size units you like, including pixels, percentages, ems, viewport units, etc.

Two Values

If you provide two values, the first sets the background image’s width and the second sets the height. Like the single value syntax, you can use whatever measurement units you like.

Multiple Images

You can also combine any of the above methods and apply them to multiple images, simply by adding commas between each syntax. Example:

html {   background: url(greatimage.jpg), url(wonderfulimage.jpg);   background-size: 300px 100px, cover;   /* first image is 300x100, second image covers the whole area */ }

Keep background image stacking order in mind when using multiple images.

Demo

This demo shows examples of cover, contain, and multiple background images with a mix of pixel and keyword values.

See the Pen background-size by CSS-Tricks (@css-tricks) on CodePen.

Related

More Resources

Browser Support

Chrome Safari Firefox Opera IE Android iOS
3+ 4.1+ 3.6+ 10+ 9+ 2.3+ 4.0+

The post A Complete Guide to CSS Gradients appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

A Complete Guide to CSS Media Queries

Media queries are a way to target browser by certain characteristics, features, and user preferences, then apply styles or run other code based on those things. Perhaps the most common media queries in the world are those that target particular viewport ranges and apply custom styles, which birthed the whole idea of responsive design.

/* When the browser is at least 600px and above */ @media screen and (min-width: 600px) {   .element {     /* Apply some styles */   } }

There are lots of other things we can target beside viewport width. That might be screen resolution, device orientation, operating system preference, or even more among a whole bevy of things we can query and use to style content.

Looking for a quick list of media queries based on the viewports of standard devices, like phones, tablets and laptops? Check out our collection of snippets.

Using media queries

Media queries are commonly associated with CSS, but they can be used in HTML and JavaScript as well.

HTML

There are a few ways we can use media queries directly in HTML.

There’s the <link> element that goes right in the document <head>. In this example. we’re telling the browser that we want to use different stylesheets at different viewport sizes:

<html>   <head>     <!-- Served to all users -->     <link rel="stylesheet" href="all.css" media="all" />     <!-- Served to screens that are at least 20em wide -->     <link rel="stylesheet" href="small.css" media="(min-width: 20em)" />     <!-- Served to screens that are at least 64em wide -->     <link rel="stylesheet" href="medium.css" media="(min-width: 64em)" />     <!-- Served to screens that are at least 90em wide -->     <link rel="stylesheet" href="large.css" media="(min-width: 90em)" />     <!-- Served to screens that are at least 120em wide -->     <link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" />     <!-- Served to print media, like printers -->     <link rel="stylesheet" href="print.css" media="print" />   </head>   <!-- ... --> </html>

Why would you want to do that? It can be a nice way to fine-tune the performance of your site by splitting styles up in a way that they’re downloaded and served by the devices that need them.

But just to be clear, this doesn’t always prevent the stylesheets that don’t match those media queries from downloading, it just assigns them a low loading priority level. So, if a small screen device like a phone visits the site, it will only download the stylesheets in the media queries that match its viewport size. But if a larger desktop screen comes along, it will download the entire bunch because it matches all of those queries (well, minus the print query in this specific example).

That’s just the <link> element. As our guide to responsive images explains, we can use media queries on <source> element, which informs the <picture> element what version of an image the browser should use from a set of image options.

<picture>   <!-- Use this image if the screen is at least 800px wide -->   <source srcset="cat-landscape.png" media="(min-width: 800px)">   <!-- Use this image if the screen is at least 600px wide -->   <source srcset="cat-cropped.png" media="(min-width: 600px)">    <!-- Use this image if nothing matches -->   <img src="cat.png" alt="A calico cat with dark aviator sunglasses."> </picture>

Again, this can be a nice performance win because we can serve smaller images to smaller devices — which presumably (but not always) will be low powered devices that might be limited to a data plan.

And let’s not forget that we can use media queries directly on the <style> element as well:

<style>   p {     background-color: blue;     color: white;   } </style>  <style media="all and (max-width: 500px)">   p {     background-color: yellow;     color: blue;   } </style>
CSS

Again, CSS is the most common place to spot a media query in the wild. They go right in the stylesheet in an @media rule that wraps elements with conditions for when and where to apply a set of styles when a browser matches those conditions.

/* Viewports between 320px and 480px wide */ @media only screen and (min-device-width: 320px) and (max-device-width: 480px)   .card {     background: #bada55;   } }

It’s also possible to scope imported style sheet but as a general rule avoid using @import since it performs poorly.

/* Avoid using @import if possible! */  /* Base styles for all screens */ @import url("style.css") screen; /* Styles for screens in a portrait (narrow) orientation */ @import url('landscape.css') screen and (orientation: portrait); /* Print styles */ @import url("print.css") print;
JavaScript

We can use media queries in JavaScript, too! And guess, what? They’re work a lot like they do in CSS. The difference? We start by using the window.matchMedia() method to define the conditions first.

So, say we want to log a message to the console when the browser is at least 768px wide. We can create a constant that calls matchMedia() and defines that screen width:

// Create a media condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia( '( min-width: 768px )' )

Then we can fire log to the console when that condition is matched:

// Create a media condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia( '( min-width: 768px )' ) 
 // Check if the media query is true if ( mediaQuery ) {   // Then log the following message to the console   console.log('Media Query Matched!') }

Unfortunately, this only fires once so if the alert is dismissed, it won’t fire again if we change the screen width and try again without refreshing. That’s why it’s a good idea to use a listener that checks for updates.

// Create a condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia('(min-width: 768px)') 
 function handleTabletChange(e) {   // Check if the media query is true   if (e) {     // Then log the following message to the console     console.log('Media Query Matched!')   } } 
 // Register event listener mediaQuery.addListener(handleTabletChange)  // Initial check handleTabletChange(mediaQuery)

Check out Marko Ilic’s full post on “Working with JavaScript Media Queries” for a deeper dive on this, including a comparison of using media queries with an older JavaScript approach that binds a resize event listener that checks window.innerWidth or window.innerHeight to fire changes.


Anatomy of a Media Query

Now that we’ve seen several examples of where media queries can be used, let’s pick them apart and see what they’re actually doing.

@media
@media [media-type] ([media-feature]) {   /* Styles! */ }

The first ingredient in a media query recipe is the @media rule itself, which is one of many CSS at-rules. Why does @media get all the attention? Because it’s geared to the type of media that a site is viewed with, what features that media type supports, and operators that can be combined to mix and match simple and complex conditions alike.

Media types
@media screen {   /* Styles! */ }

What type of media are we trying to target? In many (if not most) cases, you’ll see a screen value used here, which makes sense since many of the media types we’re trying to match are devices with screens attached to them.

But screens aren’t the only type of media we can target, of course. We have a few, including:

  • all: Matches all devices
  • print: Matches documents that are viewed in a print preview or any media that breaks the content up into pages intended to print.
  • screen: Matches devices with a screen
  • speech: Matches devices that read the content audibly, such as a screenreader. This replaces the now deprecated aural type since Media Queries Level 4.

To preview print styles in a screen all major browsers can emulate the output of a print stylesheet using DevTools. Other media types such as tty, tv,  projection,  handheld, braille, embossed and aural have been deprecated and, while the spec continues to advise browsers to recognize them, they must evaluate to nothing. If you are using one of these consider changing it for a modern approach.

Media features

Once we define the type of media we’re trying to match, we can start defining what features we are trying to match it to. We’ve looked at a lot of examples that match screens to width, where screen is the type and both min-width and max-width are features with specific values.

But there are many, many (many!) more “features” we can match. Media Queries Level 4 groups 18 media features into 5 categories.

Viewport/Page Characteristics

Feature Summary Values Added
width Defines the widths of the viewport. This can be a specific number (e.g. 400px) or a range (using min-width and max-width). <length>
height Defines the height of the viewport. This can be a specific number (e.g. 400px) or a range (using min-height and max-height). <length>
aspect-ratio Defines the width-to-height aspect ratio of the viewport <ratio>
orientation The way the screen is oriented, such as tall (portrait) or wide (landscape) based on how the device is rotated. portrait

landscape

overflow-block Checks how the device treats content that overflows the viewport in the block direction, which can be scroll (allows scrolling), optional-paged (allows scrolling and manual page breaks), paged (broken up into pages), and none (not displayed). scroll

optional-paged

paged

Media Queries Level 4
overflow-inline Checks if content that overflows the viewport along the inline axis be scrolled, which is either none (no scrolling) or scroll (allows scrolling). scroll

none

Media Queries Level 4

Display Quality

Feature Summary Values Added
resolution Defines the target pixel density of the device <resolution>

infinite

scan Defines the scanning process of the device, which is the way the device paints an image onto the screen (where interlace draws odd and even lines alternately, and progressive draws them all in sequence). interlace

progressive

grid Determines if the device uses a grid (1) or bitmap (0) screen 0 = Bitmap
1 = Grid
Media Queries Level 5
update Checks how frequently the device can modify the appearance of content (if it can at all), with values including none, slow and fast. slow

fast

none

Media Queries Level 4
environment-blending A method for determining the external environment of a device, such as dim or excessively bright places. opaque

additive

subtractive

display-mode Tests the display mode of a device, including fullscreen(no browsers chrome), standalone (a standalone application), minimal-ui (a standalone application, but with some navigation), and browser (a more traditional browser window) fullscreen

standalone

minimal-ui

browser

Web App Manifest

Color

Feature Summary Values Added
color Defines the color support of a device, expressed numerically as bits. So, a value of 12 would be the equivalent of a device that supports 12-bit color, and a value of zero indicates no color support. <integer>
color-index Defines the number of values the device supports. This can be a specific number (e.g. 10000) or a range (e.g. min-color-index: 10000, max-color-index: 15000), just like width. <integer>
monochrome The number of bits per pixel that a device’s monochrome supports, where zero is no monochrome support. <integer>
color-gamut Defines the range of colors supported by the browser and device, which could be srgb, p3 or rec2020 srgb

p3

rec2020

Media Queries Level 4
dynamic-range The combination of how much brightness, color depth, and contrast ratio supported by the video plane of the browser and user device. standard

high

inverted-colors Checks if the browser or operating system is set to invert colors (which can be useful for optimizing accessibility for sight impairments involving color) inverted

none

Media Queries Level 5

Interaction

Feature Summary Values Added
pointer Sort of like any-pointer but checks if the primary input mechanism is a pointer and, if so, how accurate it is (where coarse is less accurate, fine is more accurate, and none is no pointer). coarse

fine

none

Media Queries Level 4
hover Sort of like any-hover but checks if the primary input mechanism (e.g. mouse of touch) allows the user to hover over elements hover

none

Media Queries Level 4
any-pointer Checks if the device uses a pointer, such as a mouse or styles, as well as how accurate it is (where coarse is less accurate and fine is more accurate) coarse

fine

none

Media Queries Level 4
any-hover Checks if the device is capable of hovering elements, like with a mouse or stylus. In some rare cases, touch devices are capable of hovers. hover

none

Media Queries Level 4

Video Prefixed

The spec references user agents, including TVs, that render video and graphics in two separate planes that each have their own characteristics. The following features describe those planes.

Feature Summary Values Added
video-color-gamut Describes the approximate range of colors supported by the video plane of the browser and user device srgb

p3

rec2020

Media Queries Level 5
video-dynamic-range The combination of how much brightness, color depth, and contrast ratio supported by the video plane of the browser and user device. standard

high

Media Queries Level 5
video-width¹ The width of the video plane area of the targeted display <length> Media Queries Level 5
video-height¹ The height of the video plane area of the targeted display <length> Media Queries Level 5
video-resolution¹ The resolution of the video plane area of the targeted display <resolution>

inifinite

Media Queries Level 5
¹ Under discussion (Issue #5044)

Scripting

Feature Summary Values Added
scripting Checks whether the device allows scripting (i.e. JavaScript) where enabled allows scripting, iniital-only enabled

initial-only

Media Queries Level 5

User Preference

Feature Summary Values Added
prefers-reduced-motion Detects if the user’s system settings are set to reduce motion on the page, which is a great accessibility check. no-preference

reduce

Media Queries Level 5
prefers-reduced-transparency Detects if the user’s system settings prevent transparent across elements. no-preference

reduce

Media Queries Level 5
prefers-contrast Detects if the user’s system settings are set to either increase or decrease the amount of contrast between colors. no-preference

high

low

forced

Media Queries Level 5
prefers-color-scheme Detects if the user prefers a light or dark color scheme, which is a rapidly growing way to go about creating “dark mode” interfaces. light

dark

Media Queries Level 5
forced-colors Tests whether the browser restricts the colors available to use (which is none or active) active

none

Media Queries Level 5
prefers-reduced-data Detects if the user prefers to use less data for the page to be rendered. no-preference

reduce

Media Queries Level 5

Deprecated

Name Summary Removed
device-aspect-ratio The width-to-height aspect ratio of the output device Media Queries Level 4
device-height The height of the device’s surface that displays rendered elements Media Queries Level 4
device-width The width of the device’s surface that displays rendered elements Media Queries Level 4
Operators

Media queries support logical operators like many programming languages so that we can match media types based on certain conditions. The @media rule is itself a logical operator that is basically stating that “if” the following types and features are matches, then do some stuff.

and

But we can use the and operator if we want to target screens within a range of widths:

/* Matches screen between 320px AND 768px */ @media screen (min-width: 320px) and (max-width: 768px) {   .element {     /* Styles! */   } }

or (or comma-separated)

We can also comma-separate features as a way of using an or operator to match different ones:

/*    Matches screens where either the user prefers dark mode or the screen is at least 1200px wide */ @media screen (prefers-color-scheme: dark), (min-width 1200px) {   .element {     /* Styles! */   } }

not

Perhaps we want to target devices by what they do not support or match. This declaration removes the body’s background color when the device is a printer and can only show one color.

@media print and ( not(color) ) {   body {     background-color: none;   } }

Do you really need a media queries?

Media Queries is a powerful tool in your CSS toolbox with exciting hidden gems. But if you accomodate your design to every possible situation you’ll end up with a codebase that’s too complex to maintain and, as we all know, CSS is like a bear cub: cute and inoffensive but when it grows it will eat you alive.

That’s why I recommend following Ranald Mace’s concept of Universal Design which is “the design of products to be usable by all people, to the greatest extent possible, without the need for adaptation or specialized design.” 

On Accessibility for Everyone Laura Kalbag explains that the difference between accessible and universal design is subtle but important. An accessible designer would create a large door for people on a wheel chair to enter, while a universal designer would produce an entry that anyone would fit disregarding of their abilities.

I know that talking about universal design on the web is hard and almost sound utopian, but think about it, there are around 150 different browsers, around 50 different combinations of user preferences, and as we mentioned before more than 24000 different and unique Android devices alone. This means that there are at least 18 million possible cases in which your content might be displayed. In the words of the fantastic Miriam Suzanne “CSS out here trying to do graphic design of unknown content on an infinite and unknown canvas, across operating systems, interfaces, & languages. There’s no possible way for any of us to know what we’re doing.”

That’s why assuming is really dangerous, so when you design, develop and think about your products leave assumptions behind and use media queries to make sure that your content is displayed correctly in any contact and before any user.


Using min- and max- to match value ranges

Many of the media features outlined in the previous section — including widthheight, color and color-index — can be prefixed with min- or max- to express minimum or maximum constraints. We’ve already seen these in use throughout many of the examples, but the point is that we can create a range of value to match instead of having to declare specific values.

In the following snippet, we’re painting the body’s background purple when the viewport width is wider than 30em and narrower than 80em. If the viewport width does not match that range of values, then it will fallback to white.

body {   background-color: #fff; }  @media (min-width: 30em) and (max-width: 80em) {   body {     background-color: purple;   } }

Media Queries Level 4 specifies a new and simpler syntax using less then (>), greater than (<) and equals (=) operators. Unfortunately, at the time of writing, it isn’t supported by any major browser.


Nesting and complex decision making

CSS allows you to nest at-rules or group statements using parentheses, making it possible to go as deep as we want to evaluate complex operations.

@media (min-width: 20em), not all and (min-height: 40em) {     @media not all and (pointer: none) { ... }   @media screen and ( (min-width: 50em) and (orientation: landscape) ), print and ( not (color) ) { ... } }

Be careful! even thought it’s possible to create powerful and complex expressions, you might end up with a very opinionated, hard to maintain query. As Brad Frost puts it: “The more complex our interfaces are, the more we have to think to maintain them properly.”


Accessibility

Many of the features added in Media Queries Level 4 are centered around accessibility.

prefers-reduced-motion

prefers-reduced-motion detects if the user has the reduced motion preference activated to minimize the amount of movements and animations. It takes two values:

  • no-preference: Indicates that the user has made no preference known to the system.
  • reduce: Indicates that user has notified the system that they prefer an interface that minimizes the amount of movement or animation, preferably to the point where all non-essential movement is removed.

This preference is generally used by people who suffer from vestibular disorder or vertigo, where different movements result in loss of balance, migraine, nausea or hearing loss. If you ever tried to spin quickly and got dizzy, you know what it feels like.

In a fantastic article by Eric Bailey, he suggests stopping all animations with this code:

@media screen and (prefers-reduced-motion: reduce) {     * {     /* Very short durations means JavaScript that relies on events still works */     animation-duration: 0.001ms !important;     animation-iteration-count: 1 !important;     transition-duration: 0.001ms !important;   } }

Popular frameworks like Bootstrap have this feature on by default. In my opinion there is no excuse not to use prefers-reduced-motion — just use it. 

prefers-contrast

The prefers-contrast feature informs whether the user has chosen to increase or reduce contrast in their system preferences or the browser settings. It takes three values:

  • no-preference: When a user has made no preference known to the system. If you use it as a boolean it’ll evaluate false.
  • high: When a user has selected the option to display a higher level of contrast.
  • low: When a user has selected the option to display a lower level of contrast.

At the moment of writing this feature is not supported by any browser. Microsoft has done a non-standard earlier implementation with the -ms-high-contrast feature that works only on Microsoft Edge v18 or earlier (but not Chromium-based versions).

.button {   background-color: #0958d8;   color: #fff; }  @media (prefers-contrast: high) {   .button {     background-color: #0a0db7;   } }

This example is increasing the contrast of a the class button from AA to AAA when the user has high contrast on.

inverted-colors

The inverted-colors feature informs whether the user has chosen to invert the colors on their system preferences or the browser settings. Sometimes this option is used as an alternative to high contrast. It takes three values:

  • none: When colors are displayed normally
  • inverted: When a user has selected the option to invert colors

The problem with invested colors is that it’ll also invert the colors of images and videos, making them look like x-ray images. By using a CSS invert filter you can select all images and videos and invert them back.

@media (inverted-colors) {   img, video {      filter: invert(100%);   } }

At the time of writing this feature is only supported by Safari.

prefers-color-scheme

Having a “dark mode” color scheme is something we’re seeing a lot more of these days, and thanks to the prefers-color-scheme feature, we can tap into a user’s system or browser preferences to determine whether we serve a “dark” or a “light” theme based on the ir preferences.

It takes two values:

  • light: When a user has selected that they prefer a light theme or has no active preferences
  • dark: When a user has selected a dark display in their settings
body {   --bg-color: white;    --text-color: black;    background-color: var(--bg-color);   color: var(--text-color); }  @media screen and (prefers-color-scheme: light) {   body {     --bg-color: black;     --text-color:white;   } }

As Adhuham explains in the complete guide to Dark Mode there is way more to it than just changing the color of the background. Before you jump into doing dark mode remember that if you don’t have a very smart implementation strategy you might end up with a code base that’s really hard to maintain. CSS variables can do wonders for it but that’s a subject for another article.


What lies ahead?

Media Queries Level 5 is currently in Working Draft status, which means a lot can change between now and when it becomes a recommendation. But it includes interesting features that are worth mentioning because they open up new ways to target screens and adapt designs to very specific conditions.

User preference media features

Hey, we just covered these in the last section! Oh well. These features are exciting because they’re informed by a user’s actual settings, whether they are from the user agent or even at the operating system level.

Detecting a forced color palette

This is neat. Some browsers will limit the number of available colors that can be used to render styles. This is called “forced colors mode” and, if enabled in the browser settings, the user can choose a limited set of colors to use on a page. As a result, the user is able to define color combinations and contrasts that make content more comfortable to read.

The forced-colors feature allows us to detect if a forced color palette is in use with the active value. If matched, the browser must provide the required color palette through the CSS system colors. The browser is also given the leeway to determine if the background color of the page is light or dark and, if appropriate, trigger the appropriate prefers-color-scheme value so we can adjust the page.

Detecting the maximum brightness, color depth, and contrast ratio

Some devices (and browsers) are capable of super bright displays, rendering a wide range of colors, and high contrast ratios between colors. We can detect those devices using the dynamic-range feature, where the high keyword matches these devices and standard matches everything else.

We’re likely to see changes to this because, as of right now, there’s still uncertainty about what measurements constitute “high” levels of brightness and contrast. The browser may get to make that determination.

Video prefixed features

The spec talks about some screens, like TVs, that are capable of displaying video and graphics on separate “planes” which might be a way of distinguishing the video frame from other elements on the screen. As such, Media Queries Level 5 is proposing a new set of media features aimed at detecting video characteristics, including color gamut and dynamic range.

There are also proposals to detect video height, width and resolution, but the jury’s still out on whether those are the right ways to address video.


Browser support

Browsers keep evolving and since by the time you are reading this post chances are that browser support for this feature might change, please check MDN updated browser compatibility table.


A note on container queries

Wouldn’t be cool if components could adapt themselves on their own size instead of the browser’s? That’s what the concept of container queries is all about. We currently only have the browser screen to make those changes via media queries. That’s unfortunate, as the viewport isn’t always a direct relationship to how big the element itself is. Imagine a widget that renders in many different contexts on a site: sometimes in a sidebar, sometimes in a full-width footer, sometimes in a grid with unknown columns.

This is the problem that the elusive container queries idea is trying to solve. Ideally we could adapt styles of an element according to the size of itself instead of of the size of the viewport. But container queries don’t exist yet. The WICG is looking for use cases and it’s a highly requested feature. We see occasional movement, but it’s unsure if we’ll ever get it. But when we do, you can bet that it will have an impact on how we approach media queries as well.

In the meantime, you can catch up on the origin story of container queries for more context.


Examples

Let’s look at a bunch of media query examples. There are so many combinations of media types, features, and operators that the number of possibilities we could show would be exhaustive. Instead, we’ll highlight a handful based on specific media features.

Adjust layout at different viewport widths

More info

This is the probably the most widely used media feature. It informs the width of the browser’s viewport including the scrollbar. It unlocked the CSS implementation of what Ethan Marcotte famously coined responsive design: a process by which a design responds to the size of the viewport using a combination of a fluid grid, flexible images, and responsive typesetting.

Later, Luke Wroblewski evolved the concept of responsive design by introducing the term mobile-first, encouraging designers and developers to start with the small-screen experience first then progressively enhance the experience as the screen width and device capabilities expand. A mobile-first can usually be spotted by it’s use of min-width instead of max-width. If we start with min-width, we’re essentially saying, “hey, browser, start here and work up.” On the flip side, max-width is sort of like prioritizing larger screens.

One approach for defining breakpoints by width is using the dimensions of standard devices, like the exact pixel width of an iPhone. But there are many, many (many), many different phones, tables, laptops, and desktops. Looking at Android alone, there are more than 24,000 variations of viewport sizes, resolutions, operating systems, and browsers, as of August 2015. So, while targeting the precise width of a specific device might be helpful for troubleshooting or one-off fixes, it’s probably not the most robust solution for maintaining a responsive architecture. This isn’t a new idea by any stretch. Brad Frost was already preaching the virtues of letting content — not devices — determine breakpoints in his post “7 habits of highly effective media queries” published back in 2013.

And even though media queries are still a valid tool to create responsive interfaces, there are many situations where it’s possible to avoid using width at all. Modern CSS allow us to create flexible layouts with CSS grid and flex that adapts our content to the viewport size without a need to add breakpoints. For example, here is a grid layout that adapts how many columns it will have without any media queries at all.

.container {   display: grid;   grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }

There are many articles about thinking beyond width, I wrote about it a few years ago and I recommend checking out Una Kravet’s Ten modern layouts in one line of CSS.


Dark mode

More info

This example is pulled straight from our Guide to Dark Mode on the Web. The idea is that we can detect whether a user’s system settings are configured to light or dark mode using the prefers-color-scheme feature and then define an alternate set of colors for the rendered UI.

Combining this technique with CSS custom properties makes things even easier because they act like variables that we only need to define once, then use throughout the code. Need to swap colors? Change the custom property value and it updates everywhere. That’s exactly what prefers-color-scheme does. We define a set of colors as custom properties, then redefine them inside a media query using the prefer-color-scheme feature to change colors based on the user’s settings.


Detecting orientation, hover and motion on a responsive card gallery

More info

This gallery is responsive without using the width feature.

It detects the orientation of the viewport. If it’s a portrait viewport, the sidebar will became a header; if it’s landscape it stays off to the side.

Using the pointer media feature, it decides if the main input device is coarse — like a finger — or fine — like a mouse cursor — to set the size of the clickable areas of the checkboxes.

Then, by using the hover media feature, the example checks if the device is capable of hovering (like a mouse cursor) and display a checkbox in each card.

The animations are removed when prefers-reduced-motion is set to reduce.

And did you notice something? We’re actually not using media queries for the actual layout and sizing of the cards! That’s handled using the minmax() function on the .container element to show how responsive design doesn’t always mean using media queries.

In short, this is a fully responsive app without ever measuring width or making assumptions.

Target an iPhone in landscape mode

/* iPhone X Landscape */ @media only screen    and (min-device-width: 375px)    and (max-device-width: 812px)    and (-webkit-min-device-pixel-ratio: 3)   and (orientation: landscape) {    /* Styles! */ }
More info

The orientation media feature tests whether a device is rotated the wide way (landscape) or the tall way (portrait).

While media queries are unable to know exactly which device is being used, we can use the exact dimensions of a specific device. The snippet above is targets the iPhone X.

Apply a sticky header for large viewports

More info

In the example above, we’re using height to detached fixed elements and avoid taking up too much screen real estate when the screen is too short. A horizontal navigation bar is in a fixed position when the screen is tall, but detaches itself on shorter screens.

Like the width feature, height detects the height of the viewport, including the scrollbar. Many of us browse the web on small devices with narrow viewports, making designing for different heights more relevant than ever. Anthony Colangelo describes how Apple uses the height media feature in a meaningful way to deal with the size of the hero image as the viewport’s height changes.


Responsive (fluid) typography

More info

A font can look either too big or too small, depending on the size of the screen that’s showing it. If we’re working on a small screen, then chances are that we’ll want to use smaller type than what we’d use on a much larger screen.

The idea here is that we’re using the browser’s width to scale the font size. We set a default font size on the <html> that acts as the “small” font size, then set another font size using a media query that acts as the “large” font size. In the middle? We set the font size again, but inside another media query that calculates a size based on the browser width.

The beauty of this is that it allows the font size to adjust based on the browser width, but never go above or below certain sizes. However, there is a much simpler way to go about this that requires no media queries at all, thanks to newer CSS features, like min(), max(), and clamp().


Provide bigger touch targets when devices have a course pointer

More info

Have you ever visited a site that had super tiny buttons? Some of us have fat fingers making it tough to tap an object accurately without inadvertently tapping something else instead.

Sure, we can rely on the width feature to tell if we’re dealing with a small screen, but we can also detect if the device is capable of hovering over elements. If it isn’t then it’s probably a touch device, or perhaps a device that supports both, like the Microsoft Surface.

The demo above uses checkboxes as an example. Checkboxes can be a pain to tap on when viewing them on a small screen, so we’re increasing the size and not requiring a hover if the device is incapable of hover events.

Again, this approach isn’t always accurate. Check out Patrick Lauke’s thorough article that details potential issues working with hover, pointer, any-hover and any-pointer.

Specifications


Special thanks to Sarah Rambacher who helped to review this guide.


The post A Complete Guide to CSS Media Queries appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

Stroke Text CSS: The Definitive Guide

Whenever I think of stroked text on the web I think: nope.

There is -webkit-text-stroke in CSS for it, but it places that stroke in the middle of the vector outline of the characters, absolutely ensuring that the character doesn’t look right. Just look at this in Chrome or Safari. Gross. If you’re going to do it, at least layer the correct type on top so it has its original integrity. And even then, well, it’s non-standard.

John Negoita covers text stroke in a bunch of other ways. Another way to fake it is to use text-shadow in multiple directions.

Four ways, like the figure above, doesn’t usually cut it, so he gets mathy with it. SVG is capable os doing strokes, which you’d think would be much smarter, but it has the same exact problem as CSS does with the straddled stroke — only with somewhat more control.

I’d probably avoid stroked text on the web in general, unless it’s just a one-off, in which case I’d make it into SVG in design software, fake the stroke, and use it as a background-image.

Direct Link to ArticlePermalink


The post Stroke Text CSS: The Definitive Guide appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

How-to guide for creating edge-to-edge color bars that work with a grid

Hard-stop gradients are one of my favorite CSS tricks. Here, Marcel Moreau combines that idea with CSS grid to solve an issue that’s otherwise a pain in the butt. Say you have like a 300px right sidebar on a desktop layout with a unique background color. Easy enough. But then say you want that background color to stretch to the right edge of the browser window even though the grid itself is width-constrained. Tricker.

Direct Link to ArticlePermalink

The post How-to guide for creating edge-to-edge color bars that work with a grid appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

A Complete Guide to Dark Mode on the Web

Dark mode has gained a lot of traction recently. Like Apple, for instance, has added dark mode to its iOS and MacOS operating systems. Windows and Google have done the same. 

DuckDuckGo’s light and dark themes

Let’s get into dark mode in the context of websites. We’ll delve into different options and approaches to implementing a dark mode design and the technical considerations they entail. We’ll also touch upon some design tips along the way.


Toggling Themes

The typical scenario is that you already have a light theme for your site, and you’re interested in making a darker counterpart. Or, even if you’re starting from scratch, you’ll have both themes: light and dark. One theme should be defined as the default that users get on first visit, which is the light theme in most cases (though we can let the user’s browser make that choice for us, as we’ll see). There also should be a way to switch to the other theme (which can be done automatically, as we’ll also see) — as in, the user clicks a button and the color theme changes.

There several approaches to go about doing this:

Using a Body Class

The trick here is to swap out a class that can be a hook for changing a style anywhere on the page.

<body class="dark-theme || light-theme">

Here’s a script for a button that will toggle that class, for example:

// Select the button const btn = document.querySelector('.btn-toggle');  // Listen for a click on the button btn.addEventListener('click', function() {   // Then toggle (add/remove) the .dark-theme class to the body   document.body.classList.toggle('dark-theme');   })

Here’s how we can use that idea:

<body>   <button class="btn-toggle">Toggle Dark Mode</button>   <h1>Hey there! This is just a title</h2>   <p>I am just a boring text, existing here solely for the purpose of this demo</p>   <p>And I am just another one like the one above me, because two is better than having only one</p>   <a href="#">I am a link, don't click me!</a> </body>

The general idea of this approach is to style things up as we normally would, call that our “default” mode, then create a complete set of color styles using a class set on the <body>  element we can use as a “dark” mode.

Let’s say our default is a light color scheme. All of those “light” styles are written exactly the same way you normally write CSS. Given our HTML, let’s apply some global styling to the body and to links.

body {   color: #222;   background: #fff; } a {   color: #0033cc; }

Good good. We have dark text (#222) and dark links (#0033cc) on a light background (#fff). Our “default” theme is off to a solid start.

Now let’s redefine those property values, this time set on a different body class:

body {   color: #222;   background: #fff; } a {   color: #0033cc; } 
 /* Dark Mode styles */ body.dark-theme {   color: #eee;   background: #121212; } body.dark-theme a {   color: #809fff; }

Dark theme styles will be descendants of the same parent class — which is .dark-theme in this example — which we’ve applied to the <body> tag.

How do we “switch” body classes to access the dark styles? We can use JavaScript! We’ll select the button class (.btn-toggle), add a listener for when it’s clicked, then add the dark theme class (.dark-theme) to the body element’s class list. That effectively overrides all of the “light” colors we set, thanks to the cascade and specificity. 

Here’s the complete code working in action. Click the toggle button to toggle in and out of dark mode.

Using Separate Stylesheets

Rather than keeping all the styles together in one stylesheet, we could instead toggle between stylesheets for each theme. This assumes you have full stylesheets ready to go.

For example, a default light theme like light-theme.css:

/* light-theme.css */ 
 body {   color: #222;   background: #fff; } a {   color: #0033cc; }

Then we create styles for the dark theme and save them in a separate stylesheet we’re calling dark-theme.css.

/* dark-theme.css */ 
 body {   color: #eee;   background: #121212; } body a {   color: #809fff; }

This gives us two separate stylesheets — one for each theme — we can link up in the HTML <head> section. Let’s link up the light styles first since we’re calling those the default.

<!DOCTYPE html> <html lang="en"> <head>   <!-- Light theme stylesheet -->   <link href="light-theme.css" rel="stylesheet" id="theme-link"> </head> 
 <!-- etc. --> 
 </html>

We are using a #theme-link ID that we can select with JavaScript to, again, toggle between light and dark mode. Only this time, we’re toggling files instead of classes.

// Select the button const btn = document.querySelector(".btn-toggle"); // Select the stylesheet <link> const theme = document.querySelector("#theme-link");  // Listen for a click on the button btn.addEventListener("click", function() {   // If the current URL contains "ligh-theme.css"   if (theme.getAttribute("href") == "light-theme.css") {     // ... then switch it to "dark-theme.css"     theme.href = "dark-theme.css";   // Otherwise...   } else {     // ... switch it to "light-theme.css"     theme.href = "light-theme.css";   } });

Using Custom Properties

We can also leverage the power of CSS custom properties to create a dark theme! It helps us avoid having to write separate style rulesets for each theme, making it a lot faster to write styles and a lot easier to make changes to a theme if we need to.

We still might choose to swap a body class, and use that class to re-set custom properties:

// Select the button const btn = document.querySelector(".btn-toggle"); 
 // Listen for a click on the button btn.addEventListener("click", function() {   // Then toggle (add/remove) the .dark-theme class to the body   document.body.classList.toggle("dark-theme"); });

First, let’s define the default light color values as custom properties on the body element:

body {   --text-color: #222;   --bkg-color: #fff;   --anchor-color: #0033cc; }

Now we can redefine those values on a .dark-theme body class just like we did in the first method:

body.dark-theme {   --text-color: #eee;   --bkg-color: #121212;   --anchor-color: #809fff; }

Here are our rulesets for the body and link elements using custom properties:

body {   color: var(--text-color);   background: var(--bkg-color); } a {   color: var(--anchor-color); }

We could just as well have defined our custom properties inside the document :root. That’s totally legit and even common practice. In that case, all the default theme styles definitions would go inside :root { } and all of the dark theme properties go inside :root.dark-mode { }.

Using Server-Side Scripts

If we’re already working with a server-side language, say PHP, then we can use it instead of JavaScript. This is a great approach if you prefer working directly in the markup.

<?php $ themeClass = ''; if (isset($ _GET['theme']) && $ _GET['theme'] == 'dark') {   $ themeClass = 'dark-theme'; } 
 $ themeToggle = ($ themeClass == 'dark-theme') ? 'light' : 'dark'; ?> <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>">   <a href="?theme=<?php echo $ themeToggle; ?>">Toggle Dark Mode</a>   <!-- etc. --> </body> </html>

We can have the user send a GET or POST request. Then, we let our code (PHP in this case) apply the appropriate body class when the page is reloaded. I am using a GET request (URL params) for the purpose of this demonstration.

And, yes, we can swap stylesheets just like we did in the second method.

<?php $ themeStyleSheet = 'light-theme.css'; if (isset($ _GET['theme']) && $ _GET['theme'] == 'dark') {   $ themeStyleSheet = 'dark-theme.css'; } 
 $ themeToggle = ($ themeStyleSheet == 'dark-theme.css') ? 'light' : 'dark'; ?> <!DOCTYPE html> <html lang="en"> <head>   <!-- etc. -->   <link href="<?php echo $ themeStyleSheet; ?>" rel="stylesheet"> </head> 
 <body>   <a href="?theme=<?php echo $ themeToggle; ?>">Toggle Dark Mode</a>   <!-- etc. --> </body> </html>

This method has an obvious downside: the page needs to be refreshed for the toggle to take place. But a server-side solution like this is useful in persisting the user’s theme choice across page reloads, as we will see later.


Which method should you choose?

The “right” method comes down to the requirements of your project. If you are doing a large project, for example, you might go with CSS properties to help wrangle a large codebase. On the other hand, if your project needs to support legacy browsers, then another approach will need to do instead.

Moreover, there’s nothing saying we can only use one method. Sometimes a combination of methods will be the most effective route. There may even be other possible methods than what we have discussed.


Dark Mode at the Operating System Level

So far, we’ve used a button to toggle between light and dark mode but we can simply let the user’s operating system do that lifting for us. For example, many operating systems let users choose between light and dark themes directly in the system settings.

The “General” settings in MacOS System Preferences

Pure CSS

Details

Fortunately, CSS has a prefers-color-scheme media query which can be used to detect user’s system color scheme preferences. It can have three possible values: no preference, light and dark. Read more about it on MDN.

@media (prefers-color-scheme: dark) {   /* Dark theme styles go here */ } 
 @media (prefers-color-scheme: light) {   /* Light theme styles go here */ }

To use it, we can put the dark theme styles inside the media query.

@media (prefers-color-scheme: dark) {   body {     color: #eee;     background: #121212;   } 
   a {     color: #809fff;   } }

Now, if a user has enabled dark mode from the system settings, they will get the dark mode styles by default. We don’t have to resort to JavaScript or server-side scripts to decide which mode to use. Heck, we don’t even need the button anymore!

JavaScript

Details

We can turn to JavaScript to detect the user’s preferred color scheme. This is a lot like the first method we worked with, only we’re using matchedMedia() to detect the user’s preference.

const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');nnif (prefersDarkScheme.matches) {n  document.body.classList.add('dark-theme');n} else {n  document.body.classList.remove('dark-theme');n}

There is a downside to using JavaScript: there will likely be a quick flash of the light theme as JavaScript is executed after the CSS. That could be misconstrued as a bug.

And, of course, we can swap stylesheets instead like we did in the second method. This time, we link up both stylesheets and use the media query to determine which one is applied.

Overriding OS Settings

We just looked at how to account for a user’s system-wide color scheme preferences. But what if users want to override their system preference for a site? Just because a user prefers dark mode for their OS doesn’t always mean they prefer it on a website. That’s why providing a way to manually override dark mode, despite the system settings, is a good idea.

View Code

Let’s use the CSS custom properties approach to demonstrate how to do this. The idea is to define the custom properties for both themes like we did before, wrap dark styles up in the prefers-color-scheme media query, then define a .light-theme class inside of that we can use to override the dark mode properties, should the user want to toggle between the two modes.

/* Default colors */ body {   --text-color: #222;   --bkg-color: #fff; } /* Dark theme colors */ body.dark-theme {   --text-color: #eee;   --bkg-color: #121212; }  /* Styles for users who prefer dark mode at the OS level */ @media (prefers-color-scheme: dark) {   /* defaults to dark theme */   body {      --text-color: #eee;     --bkg-color: #121212;   }   /* Override dark mode with light mode styles if the user decides to swap */   body.light-theme {     --text-color: #222;     --bkg-color: #fff;   } }

Now we can turn back to our trusty button to toggle between light and dark themes. This way, we’re respecting the OS color preference by default and allowing the user to manually switch themes.

// Listen for a click on the button  btn.addEventListener("click", function() {   // If the OS is set to dark mode...   if (prefersDarkScheme.matches) {     // ...then apply the .light-theme class to override those styles     document.body.classList.toggle("light-theme");     // Otherwise...   } else {     // ...apply the .dark-theme class to override the default light styles     document.body.classList.toggle("dark-theme");   } });

Browser Support

The prefers-color-scheme media query feature enjoys support by major browsers, including Chrome 76+, Firefox 67+, Chrome Android 76+ and Safari 12.5+ (13+ on iOS). It doesn’t support IE and Samsung Internet Browser.

That’s a promising amount of support!. Can I Use estimates 80.85% of user coverage.

Operating systems that currently support dark mode include MacOS (Mojave or later), iOS (13.0+), Windows (10+), and Android (10+).


Storing a User’s Preference

What we’ve looked at so far definitely does what it says in the tin: swap themes based on an OS preference or a button click. This is great, but doesn’t carry over when the user either visits another page on the site or reloads the current page.

We need to save the user’s choice so that it will be applied consistently throughout the site and on subsequent visits. To do that, we can save the user’s choice to the localStorage when the theme is toggled. Cookies are also well-suited for the job.

Let’s look at both approaches.

Using localStorage

We have a script that saves the selected theme to localStorage when the toggle takes place. In other words, when the page is reloaded, the script fetches the choice from localStorage and applies it. JavaScript is often executed after CSS, so this approach is prone to a “flash of incorrect theme” (FOIT).

View Code
// Select the button const btn = document.querySelector(".btn-toggle"); // Select the theme preference from localStorage const currentTheme = localStorage.getItem("theme"); 
 // If the current theme in localStorage is "dark"... if (currentTheme == "dark") {   // ...then use the .dark-theme class   document.body.classList.add("dark-theme"); } 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // Toggle the .dark-theme class on each click   document.body.classList.toggle("dark-theme");      // Let's say the theme is equal to light   let theme = "light";   // If the body contains the .dark-theme class...   if (document.body.classList.contains("dark-theme")) {     // ...then let's make the theme dark     theme = "dark";   }   // Then save the choice in localStorage   localStorage.setItem("theme", theme); });

Using Cookies with PHP

To avoid FLIC, we can use a server-side script like PHP. Instead of saving the user’s theme preference in localStorage, we will create a cookie from JavaScript and save it there. But again, this may only be feasible if you’re already working with a server-side language.

View Code
// Select the button const btn = document.querySelector(".btn-toggle"); 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // Toggle the .dark-theme class on the body   document.body.classList.toggle("dark-theme");      // Let's say the theme is equal to light   let theme = "light";   // If the body contains the .dark-theme class...   if (document.body.classList.contains("dark-theme")) {     // ...then let's make the theme dark     theme = "dark";   }   // Then save the choice in a cookie   document.cookie = "theme=" + theme; });

We can now check for the existence of that cookie and load the appropriate theme by applying the proper class to the <body> tag.

<?php $ themeClass = ''; if (!empty($ _COOKIE['theme']) && $ _COOKIE['theme'] == 'dark') {   $ themeClass = 'dark-theme'; } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> </body> </html>

Here is how to do that using the separate stylesheets method:

<?php $ themeStyleSheet = 'light-theme.css'; if (!empty($ _COOKIE['theme']) && $ _COOKIE['theme'] == 'dark') {   $ themeStyleSheet = 'dark-theme.css'; } ?> 
 <!DOCTYPE html> <html lang="en"> <head>   <!-- etc. -->   <link href="<?php echo $ themeStyleSheet; ?>" rel="stylesheet" id="theme-link"> </head> <!-- etc. -->

If your website has user accounts — like a place to log in and manage profile stuff — that’s also a great place to save theme preferences. Send those to the database where user account details are stored. Then, when the user logs in, fetch the theme from the database and apply it to the page using PHP (or whatever server-side script).

There are various ways to do this. In this example, I am fetching the user’s theme preference from the database and saving it in a session variable at the time of login.

<?php // Login action if (!empty($ _POST['login'])) {   // etc. 
   // If the uuser is authenticated...   if ($ loginSuccess) {     // ... save their theme preference to a session variable     $ _SESSION['user_theme'] = $ userData['theme'];   } } 
 // Pick the session variable first if it's set; otherwise pick the cookie $ themeChoice = $ _SESSION['user_theme'] ?? $ _COOKIE['theme'] ?? null; $ themeClass = ''; if ($ themeChoice == 'dark') {   $ themeClass = 'dark-theme'; } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> </body> </html>

I am using PHP’s null coalesce operator (??) to decide where to pick the theme preference: from the session or from the cookie. If the user is logged in, the value of the session variable is taken instead that of the cookie. And if the user is not logged in or has logged out, the value of cookie is taken.


Handling User Agent Styles

To inform the browser UA stylesheet about the system color scheme preferences and tell it which color schemes are supported in the page, we can use the color-scheme meta tag.

For example, let’s say the page should support both “dark” and “light” themes. We can put both of them as values in the meta tag, separated by spaces. If we only want to support a “light” theme, then we only need to use “light” as the value. This is discussed in a CSSWG GitHub issue, where it was originally proposed.

<meta name="color-scheme" content="dark light">

When this meta tag is added, the browser takes the user’s color scheme preferences into consideration when rendering UA-controlled elements of the page (like a <button>). It renders colors for the root background, form controls, and spell-check features (as well as any other UA-controlled styles) based on the user’s preference.

Source

Although themes are manually styled for the most part (which overrides the UA styles), informing the browser about the supported themes helps to avoid even the slightest chance of a potential FOIT situation. This is true for those occasions where HTML has rendered but CSS is still waiting to load.

We can also set this in CSS:

:root {   color-scheme: light dark; /* both supported */ }
via Jim Nielsen

At the time of writing, the color-scheme property lacks broad browser support, though Safari and Chrome both support it.


Combining all the things!

Let’s combine everything and create a working demo that:

  1. Automatically loads a dark or light theme based on system preferences
  2. Allows the user to manually override their system preference
  3. Maintains the user’s preferred theme on page reloads

Using JavaScript & Local Storage

// Select the button const btn = document.querySelector(".btn-toggle"); // Check for dark mode preference at the OS level const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); 
 // Get the user's theme preference from local storage, if it's available const currentTheme = localStorage.getItem("theme"); // If the user's preference in localStorage is dark... if (currentTheme == "dark") {   // ...let's toggle the .dark-theme class on the body   document.body.classList.toggle("dark-mode"); // Otherwise, if the user's preference in localStorage is light... } else if (currentTheme == "light") {   // ...let's toggle the .light-theme class on the body   document.body.classList.toggle("light-mode"); } 
 // Listen for a click on the button  btn.addEventListener("click", function() {   // If the user's OS setting is dark and matches our .dark-mode class...   if (prefersDarkScheme.matches) {     // ...then toggle the light mode class     document.body.classList.toggle("light-mode");     // ...but use .dark-mode if the .light-mode class is already on the body,     var theme = document.body.classList.contains("light-mode") ? "light" : "dark";   } else {     // Otherwise, let's do the same thing, but for .dark-mode     document.body.classList.toggle("dark-mode");     var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";   }   // Finally, let's save the current preference to localStorage to keep using it   localStorage.setItem("theme", theme); });

Using PHP & Cookies

<?php $ themeClass = ''; if (!empty($ _COOKIE['theme'])) {   if ($ _COOKIE['theme'] == 'dark') {     $ themeClass = 'dark-theme';   } else if ($ _COOKIE['theme'] == 'light') {     $ themeClass = 'light-theme';   }   } ?> 
 <!DOCTYPE html> <html lang="en"> <!-- etc. --> <body class="<?php echo $ themeClass; ?>"> <!-- etc. --> <script>   const btn = document.querySelector(".btn-toggle");   const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");      btn.addEventListener("click", function() {     if (prefersDarkScheme.matches) {       document.body.classList.toggle("light-mode");       var theme = document.body.classList.contains("light-mode") ? "light" : "dark";     } else {       document.body.classList.toggle("dark-mode");       var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";     }     document.cookie = "theme=" + theme;   }); </script> </body> </html>


Design Considerations

I often hear that implementing dark mode is easier than designing one. While I’ll refrain from judgement, let’s look at some considerations for designing a dark theme.

You already know the basic task: swap lighter color values for darker ones and vice versa. But there are some UI elements and enhancements that are more nuanced and require more attention. Let’s take a look at those.

Dark Mode Images

A good rule is to decrease the brightness and contrast of images a bit so that it looks comfortable to the eyes when it’s against a dark background. A super bright image on a super dark background can be jarring and dimming the image reduces some of that heavy contrast.

The CSS filter() function is more than capable of handling this for us:

/* Apply the filter directly on the body tag */ body.dark-theme img {   filter: brightness(.8) contrast(1.2); } 
 /* Or apply it via media query */ @media (prefers-color-scheme: dark) {   img {     filter: brightness(.8) contrast(1.2);   } }

We can do the same sort of thing directly in the markup using the <picture> element to load different versions of an image:

<picture>   <!-- Use this image if the user's OS setting is light or unset -->   <source srcset="photo-light.png" media="(prefers-color-scheme: light) or (prefers-color-scheme: no-preference)">   <!-- Use this image if the user's OS setting is dark -->   <source srcset="photo-dark.png" media="(prefers-color-scheme: dark)"> </picture>

The downside here is that it requires supplying two files where we only have to deal with one when using CSS. This also doesn’t fully account for the user toggling the color theme on the site.

Dark Mode Shadows

Dark mode shadows are tricky. If we simply invert a dark shadow using light colors, then we get this funky thing with a light shadow on a dark background… and it’s not a good look.

It’s possible to use a dark shadow in dark mode, but the background color has to be “light” enough (like a dark gray) to provide enough contrast to actually see the shadow against it.

Use opacity to convey depth, with high opacity regions having a lower depth. That’s to say, elements that have a higher elevation should have a lower opacity than elements that are “closer” in depth to the background.

Different shades of color create different perceptions of “depth”

Dark Mode Typography

The trick here is a lot like images: we’ve gotta balance the contrast. Use too heavy of a font and we get blaring text that’s makes us want to move away from the screen. Use too light of a font and we’ll strain our eyes while inching toward the screen to get a closer look.

The balance is somewhere in the middle. Robin has a nice write-up where he suggests a small bit of CSS that makes a big difference in legibility.

Dark Mode Icons

Icons fall into this “tricky” category because they’re sort of a cross between text and images. If we’re working with SVG icons, though, we can change the fill with CSS. On the other hand, if we’re using font icons, we can simply change the color property instead.

/* SVG icon */ body.dark-theme svg.icon path {   fill: #efefef; } /* Font icon (using Font Awesome as an example) */ body.dark-theme .fa {   color: #efefef; }

A lot of the same design considerations that are true for text, are also generally applicable to icons. For example, we ought to avoid using full white and heavy outlines.

Dark Mode Colors

Pure white text on a pure black background will look jarring. The trick here is to use an off-white for the text and off-black for the background. Material Design Guidelines for example recommends #121212 for the background.

Dark Mode Color Palettes

We’ve seen the difference using off-white and off-black colors makes for text and images. Let’s expand on that a bit with tips on how to develop a full color palette.

Most things boil down to one thing: contrast. That’s why the first tip before settling on any color is to run ideas through a contrast checker to ensure color ratios conform to WCAG’s guidelines for at least a AA rating, which is a contrast ratio of 4.5:1.

That means desaturated colors are our friends when working with a dark mode design. They help prevent overbearingly bright images and still give us plenty of room to create an effective contrast ratio.

Next, remember that accent colors are meant to be enhancements. They’re likely brighter than the dark theme background color, so using them like a primary color or the background color of a large container is just as jarring and hard on the eyes as a bright image or heavy white text.

If contrast is the balance we’re trying to strike, then remember that dark mode is more than blacks and grays. What about dark blue background with pale yellow text? Or dark brown with tan? There’s an entire (and growing) spectrum of color out there and we can leverage any part of it to fuel creativity.

A few examples of colors that are dark without resorting to full-on black:

#232B32

#152028

#202945

Material Design’s guidelines on dark mode is a handy resource on best practices for dark mode design. It’s definitely worth a read for more tips to keep in mind.

Dark Mode in the Wild

YouTube uses the CSS variables technique. They’ve defined all their colors in variables under the html selector while dark mode colors are defined under html:not(.style-scope)[dark]. When dark mode is enabled, YouTube adds a dark="true" attribute to the <html> tag. This is what they use to override the variables defined in the HTML.

YouTube adds dark=true attribute to the <html> when it switches to the dark mode.

In the wild, the CSS custom properties approach seems to be most popular. It’s being used by Dropbox Paper, Slack, and Facebook.

Simplenote uses the class-swapping method where all light style rules are descendants of a .theme-light parent class and all the dark styles fall under a .theme-dark class. When the theme is toggled, the appropriate class is applied to the <body> tag.

Simplenote uses two classes: .light-theme and .dark-theme to style the themes.

Twitter goes the extra mile and offers several themes to choose from: “Default,” “Dim,” and “Lights out.” The “Dim” option employs dark blue for a background color. Compare that to “Lights out” which uses a stark black.

Twitter offers three themes to choose from.

Dark mode or no dark mode? That is the question.

There are perfectly valid reasons on both sides. Some of those reasons even go beyond the scope of user experience and include things like timing, budget and resources.

While being considerate of why you might not want to implement a dark mode, here are reasons why you might want to have one:

  • It’s cool and trendy (although that’s not a reason alone to do it)
  • It enhances accessibility by supporting users who are sensitive to eye strain in starkly bright themes.
  • It allows users to decide the most comfortable way to consume content while providing us a way to maintain control over the look and feel of things. Remember, we want to beat the Reader Mode button!
  • It helps to preserve battery life for devices with OLED screen where brighter colors consume more energy.
  • It’s extremely popular and appears to be going nowhere. It’s possible that your users who prefer a dark mode (like me!) will expect your site to have one. Might as well be ready for it.

The post A Complete Guide to Dark Mode on the Web appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Adding a Custom Welcome Guide to the WordPress Block Editor

I am creating a WordPress plugin and there is a slight learning curve when it comes to using it. I’d like to give users a primer on how to use the plugin, but I want to avoid diverting users to documentation on the plugin’s website since that takes them out of the experience.

What would be great is for users to immediately start using the plugin once it’s installed but have access to helpful tips while they are actively using it. There’s no native feature for something like this in WordPress but we can make something because WordPress is super flexible like that.

So here’s the idea. We’re going to bake documentation directly into the plugin and make it easily accessible in the block editor. This way, users get to use the plugin right away while having answers to common questions directly where they’re working. 

My plugin operates through several Custom Post Types (CPT). What we’re going to build is essentially a popup modal that users get when they go to these CPTs. 

The WordPress block editor is built in React, which utilizes components that can be customized to and reused for different situations.  That is the case with what we’re making — let’s call it the <Guide> component — which behaves like a modal, but is composed of several pages that the user can paginate through.

WordPress itself has a <Guide> component that displays a welcome guide when opening the block editor for the first time:

Screenshot showing a modal on top of the WordPress block editor welcoming users to the editor for the first time.
WordPress displays a modal with instructions for using the block editor when a user loads the editor for the first time.

The guide is a container filled with content that’s broken up into individual pages. In other words, it’s pretty much what we want. That means we don’t have to re-invent the wheel with this project; we can reuse this same concept for our own plugin.

Let’s do exactly that. 

What we want to achieve

Before we get to the solution, let’s talk about the end goal.

The design satisfies the requirements of the plugin, which is a GraphQL server for WordPress. The plugin offers a variety of CPTs that are edited through custom blocks which, in turn, are defined through templates. There’s a grand total of two blocks: one called “GraphiQL client” to input the GraphQL query, and one called “Persisted query options” to customize the behavior of the execution.

Since creating a query for GraphQL is not a trivial task, I decided to add the guide component to the editor screen for that CPT. It’s available in the Document settings as a panel called “Welcome Guide.”

Screenshot showing the WordPress editor with the document settings panel open in the right column. a welcome guide tab is highlighted in the settings.

Crack that panel open and the user gets a link. That link is what will trigger the modal.

Close-up screenshot of the welcome guide tab opened, revealing a link that says "Open Guide: Creating Persisted Queries."

For the modal itself, I decided to display a tutorial video on using the CPT on the first page, and then describe in detail all the options available in the CPT on subsequent pages.

Screenshot showing the custom modal open in the block editor and containing an embedded video on how to use the plugin.

I believe this layout is an effective way to show documentation to the user. It is out of the way, but still conveniently close to the action. Sure, we can use a different design or even place the modal trigger somewhere else using a different component instead of repurposing <Guide>, but this is perfectly good.

Planning the implementation

The implementation comprises the following steps:

  1. Scaffolding a new script to register the custom sidebar panel
  2. Displaying the custom sidebar panel on the editor for our Custom Post Type only
  3. Creating the guide
  4. Adding content to the guide

Let’s start!

Step 1: Scaffolding the script

Starting in WordPress 5.4, we can use a component called <PluginDocumentSettingPanel> to add a panel on the editor’s Document settings like this:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost;   const PluginDocumentSettingPanelDemo = () => (   <PluginDocumentSettingPanel     name="custom-panel"     title="Custom Panel"     className="custom-panel"   >     Custom Panel Contents   </PluginDocumentSettingPanel> ); registerPlugin( 'plugin-document-setting-panel-demo', {   render: PluginDocumentSettingPanelDemo,   icon: 'palmtree', } );

If you’re experienced with the block editor and already know how to execute this code, then you can skip ahead. I’ve been coding with the block editor for less than three months, and using React/npm/webpack is a new world for me — this plugin is my first project using them! I’ve found that the docs in the Gutenberg repo are not always adequate for beginners like me, and sometimes the documentation is missing altogether, so I’ve had to dig into the source code to find answers.

When the documentation for the component indicates to use that piece of code above, I don’t know what to do next, because <PluginDocumentSettingPanel> is not a block and I am unable to scaffold a new block or add the code there. Plus, we’re working with JSX, which means we need to have a JavaScript build step to compile the code.

I did, however, find the equivalent ES5 code:

var el = wp.element.createElement; var __ = wp.i18n.__; var registerPlugin = wp.plugins.registerPlugin; var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; 
 function MyDocumentSettingPlugin() {   return el(     PluginDocumentSettingPanel,     {       className: 'my-document-setting-plugin',       title: 'My Panel',     },     __( 'My Document Setting Panel' )   ); } 
 registerPlugin( 'my-document-setting-plugin', {   render: MyDocumentSettingPlugin } );

ES5 code does not need be compiled, so we can load it like any other script in WordPress. But I don’t want to use that. I want the full, modern experience of ESNext and JSX.

So my thinking goes like this: I can’t use the block scaffolding tools since it’s not a block, and I don’t know how to compile the script (I’m certainly not going to set-up webpack all by myself). That means I’m stuck.

But wait! The only difference between a block and a regular script is just how they are registered in WordPress. A block is registered like this:

wp_register_script($ blockScriptName, $ blockScriptURL, $ dependencies, $ version); register_block_type('my-namespace/my-block', [   'editor_script' => $ blockScriptName, ]);

And a regular script is registered like this:

wp_register_script($ scriptName, $ scriptURL, $ dependencies, $ version); wp_enqueue_script($ scriptName);

We can use any of the block scaffolding tools to modify things then register a regular script instead of a block, which gains us access to the webpack configuration to compile the ESNext code. Those available tools are:

I chose to use the @wordpress/create-block package because it is maintained by the team developing Gutenberg.

To scaffold the block, we execute this in the command line:

npm init @wordpress/block

After completing all the prompts for information — including the block’s name, title and description — the tool will generate a single-block plugin, with an entry PHP file containing code similar to this:

/**  * Registers all block assets so that they can be enqueued through the block editor  * in the corresponding context.  *  * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/  */ function my_namespace_my_block_block_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-namespace/my-block" block first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-namespace-my-block-block-editor',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   ); 
   $ editor_css = 'editor.css';   wp_register_style(     'my-namespace-my-block-block-editor',     plugins_url( $ editor_css, __FILE__ ),     array(),     filemtime( "$ dir/$ editor_css" )   ); 
   $ style_css = 'style.css';   wp_register_style(     'my-namespace-my-block-block',     plugins_url( $ style_css, __FILE__ ),     array(),     filemtime( "$ dir/$ style_css" )   ); 
   register_block_type( 'my-namespace/my-block', array(     'editor_script' => 'my-namespace-my-block-block-editor',     'editor_style'  => 'my-namespace-my-block-block-editor',     'style'         => 'my-namespace-my-block-block',   ) ); } add_action( 'init', 'my_namespace_my_block_block_init' );

We can copy this code into the plugin, and modify it appropriately, converting the block into a regular script. (Note that I’m also removing the CSS files along the way, but could keep them, if needed.)

function my_script_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-script" script first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-script',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   );   wp_enqueue_script(     'my-script'   ); } add_action( 'init', 'my_script_init' );

Let’s copy the package.json file over:

{   "name": "my-block",   "version": "0.1.0",   "description": "This is my block",   "author": "The WordPress Contributors",   "license": "GPL-2.0-or-later",   "main": "build/index.js",   "scripts": {     "build": "wp-scripts build",     "format:js": "wp-scripts format-js",     "lint:css": "wp-scripts lint-style",     "lint:js": "wp-scripts lint-js",     "start": "wp-scripts start",     "packages-update": "wp-scripts packages-update"   },   "devDependencies": {     "@wordpress/scripts": "^9.1.0"   } }

Now, we can replace the contents of file src/index.js with the ESNext code from above to register the <PluginDocumentSettingPanel> component. Upon running npm start (or npm run build for production) the code will be compiled into build/index.js.

There is a last problem to solve: the <PluginDocumentSettingPanel> component is not statically imported, but instead obtained from wp.editPost, and since wp is a global variable loaded by WordPress on runtime, this dependency is not present in index.asset.php (which is auto-generated during build). We must manually add a dependency to the wp-edit-post script when registering the script to make sure it loads before ours:

$ dependencies = array_merge(   $ script_asset['dependencies'],   [     'wp-edit-post',   ] ); wp_register_script(   'my-script',   plugins_url( $ index_js, __FILE__ ),   $ dependencies,   $ script_asset['version'] );

Now the script setup is ready!

The plugin can be updated with Gutenberg’s relentless development cycles. Run npm run packages-update to update the npm dependencies (and, consequently, the webpack configuration, which is defined on package "@wordpress/scripts") to their latest supported versions.

At this point, you might be wondering how I knew to add a dependency to the "wp-edit-post" script before our script. Well, I had to dig into Gutenberg’s source code. The documentation for <PluginDocumentSettingPanel> is somewhat incomplete, which is a perfect example of how Gutenberg’s documentation is lacking in certain places.

While digging in code and browsing documentation, I discovered a few enlightening things. For example, there are two ways to code our scripts: using either the ES5 or the ESNext syntax. ES5 doesn’t require a build process, and it references instances of code from the runtime environment, most likely through the global wp variable. For instance, the code to create an icon goes like this:

var moreIcon = wp.element.createElement( 'svg' );

ESNext relies on webpack to resolve all dependencies, which enables us to import static components. For instance, the code to create an icon would be:

import { more } from '@wordpress/icons';

This applies pretty much everywhere. However, that’s not the case for the <PluginDocumentSettingPanel> component, which references the runtime environment for ESNext:

const { PluginDocumentSettingPanel } = wp.editPost;

That’s why we have to add a dependency to the “wp-edit-post” script. That’s where the wp.editPost variable is defined.

If <PluginDocumentSettingPanel> could be directly imported, then the dependency to “wp-edit-post” would be automatically handled by the block editor through the Dependency Extraction Webpack Plugin. This plugin builds the bridge from static to runtime by creating a index.asset.php file containing all the dependencies for the runtime environment scripts, which are obtained by replacing "@wordpress/" from the package name with "wp-". Hence, the "@wordpress/edit-post" package  becomes the "wp-edit-post" runtime script. That’s how I figured out which script to add the dependency.

Step 2: Blacklisting the custom sidebar panel on all other CPTs 

The panel will display documentation for a specific CPT, so it must be registered only to that CPT. That means we need to blacklist it from appearing on any other post types.

Ryan Welcher (who created the <PluginDocumentSettingPanel> component) describes this process when registering the panel:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost const { withSelect } = wp.data; 
 const MyCustomSideBarPanel = ( { postType } ) => { 
   if ( 'post-type-name' !== postType ) {     return null;   } 
   return(     <PluginDocumentSettingPanel       name="my-custom-panel"       title="My Custom Panel"     >       Hello, World!     </PluginDocumentSettingPanel>   ); } 
 const CustomSideBarPanelwithSelect = withSelect( select => {   return {     postType: select( 'core/editor' ).getCurrentPostType(),   }; } )( MyCustomSideBarPanel); 
 
 registerPlugin( 'my-custom-panel', { render: CustomSideBarPanelwithSelect } );

He also suggests an alternative solution, using useSelect instead of withSelect.

That said, I’m not totally convinced by this solution, because the JavaScript file must still be loaded, even if it isn’t needed, forcing the website to take a performance hit. Doesn’t it make more sense to not register the JavaScript file than it does to run JavaScript just to disable JavaScript?

I have created a PHP solution. I’ll admit that it feels a bit hacky, but it works well. First, we find out which post type is related to the object being created or edited:

function get_editing_post_type(): ?string {   if (!is_admin()) {     return null;   } 
   global $ pagenow;   $ typenow = '';   if ( 'post-new.php' === $ pagenow ) {     if ( isset( $ _REQUEST['post_type'] ) && post_type_exists( $ _REQUEST['post_type'] ) ) {       $ typenow = $ _REQUEST['post_type'];     };   } elseif ( 'post.php' === $ pagenow ) {     if ( isset( $ _GET['post'] ) && isset( $ _POST['post_ID'] ) && (int) $ _GET['post'] !== (int) $ _POST['post_ID'] ) {       // Do nothing     } elseif ( isset( $ _GET['post'] ) ) {       $ post_id = (int) $ _GET['post'];     } elseif ( isset( $ _POST['post_ID'] ) ) {       $ post_id = (int) $ _POST['post_ID'];     }     if ( $ post_id ) {       $ post = get_post( $ post_id );       $ typenow = $ post->post_type;     }   }   return $ typenow; }

Then, ,we register the script only if it matches our CPT:

add_action('init', 'maybe_register_script'); function maybe_register_script() {   // Check if this is the intended custom post type   if (get_editing_post_type() != 'my-custom-post-type') {     return;   } 
   // Only then register the block   wp_register_script(...);   wp_enqueue_script(...); }

Check out this post for a deeper dive on how this works.

Step 3: Creating the custom guide

I designed the functionality for my plugin’s guide based on the WordPress <Guide> component. I didn’t realize I’d be doing that at first, so here’s how I was able to figure that out.

  1. Search the source code to see how it was done there.
  2. Explore the catalogue of all available components in Gutenberg’s Storybook.

First, I copied content from the block editor modal and did a basic search. The results pointed me to this file. From there I discovered the component is called <Guide> and could simply copy and paste its code to my plugin as a base for my own guide.

Then I looked for the component’s documentation. I browsed the @wordpress/components package (which, as you may have guessed, is where components are implemented) and found the component’s README file. That gave me all the information I needed to implement my own custom guide component.

I also explored the catalogue of all the available components in Gutenberg’s Storybook (which actually shows that these components can be used outside the context of WordPress). Clicking on all of them, I finally discovered <Guide>. The storybook provides the source code for several examples (or stories). It’s a handy resource for understanding how to customize a component through props.

At this point, I knew <Guide> would make a solid base for my component. There is one missing element, though: how to trigger the guide on click. I had to rack my brain for this one!

This is a button with a listener that opens the modal on click:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import MyGuide from './guide'; 
 const MyGuideWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button onClick={ () => setOpen( true ) }>         { __('Open Guide: “Creating Persisted Queries”') }       </Button>       { isOpen && (         <MyGuide            { ...props }           onFinish={ () => setOpen( false ) }         />       ) }     </>   ); }; export default MyGuideWithButton;

Even though the block editor tries to hide it, we are operating within React. Until now, we’ve been dealing with JSX and components. But now we need the useState hook, which is specific to React.

I’d say that having a good grasp of React is required if you want to master the WordPress block editor. There is no way around it.

Step 4: Adding content to the guide

We’re almost there! Let’s create the <Guide> component, containing a <GuidePage> component for each page of content.

The content can use HTML, include other components, and whatnot. In this particular case, I have added three <GuidePage> instances for my CPT just using HTML. The first page includes a video tutorial and the next two pages contain detailed instructions.

import { Guide, GuidePage } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; 
 const MyGuide = ( props ) => {   return (     <Guide { ...props } >       <GuidePage>         <video width="640" height="400" controls>           <source src="https://d1c2lqfn9an7pb.cloudfront.net/presentations/graphql-api/videos/graphql-api-creating-persisted-query.mov" type="video/mp4" />           { __('Your browser does not support the video tag.') }         </video>         // etc.       </GuidePage>       <GuidePage>         // ...       </GuidePage>       <GuidePage>         // ...       </GuidePage>     </Guide>   ) } export default MyGuide;
imaged gif showing the mouse cursor clicking on the Open Guide link in the block editor's document settings, which opens the custom welcome guide containing a video with links to other pages in the modal.
Hey look, we have our own guide now!

Not bad! There are a few issues, though:

  • I couldn’t embed the video inside the <Guide> because clicking the play button closes the guide. I assume that’s because the <iframe> falls outside the boundaries of the guide. I wound up uploading the video file to S3 and serving with <video>.
  • The page transition in the guide is not very smooth. The block editor’s modal looks alright because all pages have a similar height, but the transition in this one is pretty abrupt.
  • The hover effect on buttons could be improved. Hopefully, the Gutenberg team needs to fix this for their own purposes, because my CSS aren’t there. It’s not that my skills are bad; they are nonexistent.

But I can live with these issues. Functionality-wise, I’ve achieved what I need the guide to do.

Bonus: Opening docs independently 

For our <Guide>, we created the content of each <GuidePage> component directly using HTML. However, if this HTML code is instead added through an autonomous component, then it can be reused for other user interactions.

For instance, the component <CacheControlDescription> displays a description concerning HTTP caching:

const CacheControlDescription = () => {   return (     <p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or "no-store" if the max-age is 0</p>   ) } export default CacheControlDescription;

This component can be added inside a <GuidePage> as we did before, but also within a <Modal> component:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import CacheControlDescription from './cache-control-desc'; 
 const CacheControlModalWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button          icon="editor-help"         onClick={ () => setOpen( true ) }       />       { isOpen && (         <Modal            { ...props }           onRequestClose={ () => setOpen( false ) }         >           <CacheControlDescription />         </Modal>       ) }     </>   ); }; export default CacheControlModalWithButton;

To provide a good user experience, we can offer to show the documentation only when the user is interacting with the block. For that, we show or hide the button depending on the value of isSelected:

import { __ } from '@wordpress/i18n'; import CacheControlModalWithButton from './modal-with-btn'; 
 const CacheControlHeader = ( props ) => {   const { isSelected } = props;   return (     <>       { __('Cache-Control max-age') }       { isSelected && (         <CacheControlModalWithButton />       ) }     </>   ); } export default CacheControlHeader;

Finally, the <CacheControlHeader> component is added to the appropriate control.

Animated gif showing the option to view a guide displaying when a block is selected in the editor.

Tadaaaaaaaa 🎉

The WordPress block editor is quite a piece of software! I was able to accomplish things with it that I would have been unable to without it. Providing documentation to the user may not be the shiniest of examples or use cases, but it’s a very practical one and something that’s relevant for many other plugins. Want to use it for your own plugin? Go for it!

The post Adding a Custom Welcome Guide to the WordPress Block Editor appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

A Guide to the Responsive Images Syntax in HTML

This guide is about the HTML syntax for responsive images (and a little bit of CSS for good measure). The responsive images syntax is about serving one image from multiple options based on rules and circumstances. There are two forms of responsive images, and they’re for two different things:

If your only goal is…

Increased Performance

Then what you need is…

<img   srcset=""   src=""   alt="" >

There is a lot of performance gain to be had by using responsive images. Image weight has a huge impact on pages’ overall performance, and responsive images are one of the best things that you can do to cut image weight. Imagine the browser being able to choose between a 300×300 image or a 600×600. If the browser only needs the 300×300, that’s potentially a 4× bytes-over-the-wire savings! Savings generally go up as the display resolution and viewport size go down; on the smallest screens, a couple of case studies have shown byte savings of 70–90%.

If you also need…

Design Control

Then what you need is…

<picture>   <source srcset="" media="">   <source srcset="" media="">   <img src="" alt=""> </picture>

Another perfectly legit goal with responsive images is not just to serve different sizes of the same image, but to serve different images. For example, cropping an image differently depending on the size of the screen and differences in the layout. This is referred to as “art direction.”

The <picture> element is also used for fallback image types and any other sort of media query switching (e.g. different images for dark mode). You get greater control of what browsers display.


There is a lot to talk about here, so let’s go through both syntaxes, all of the related attributes and values, and talk about a few related subjects along the way, like tooling and browsers.


Using srcset

The <img srcset="" src="" alt=""> syntax is for serving differently-sized versions of the same image. You could try to serve entirely different images using this syntax, but browsers assume that everything in a srcset is visually-identical and will choose whichever size they think is best, in impossible-for-you-to-predict ways. So I wouldn’t reccomend it.

Perhaps the easiest-possible responsive images syntax is adding a srcset attribute with x descriptors on the images to label them for use on displays with different pixel-densities.

<img    alt="A baby smiling with a yellow headband."   src="baby-lowres.jpg"   srcset="baby-highres.jpg 2x" >

Here, we’ve made the default (the src) the “low res” (1×) copy of the image. Defaulting to the smallest/fastest resources is usually the smart choice:. We also provide a 2× version. If the browser knows it is on a higher pixel-density display (the 2x part), it will use that image instead.

Demo
<img    alt="A baby smiling with a yellow headband."   src="baby-lowres.jpg"   srcset="     baby-high-1.jpg 1.5x,     baby-high-2.jpg 2x,     baby-high-3.jpg 3x,     baby-high-4.jpg 4x,     baby-high-5.jpg 100x   " >

You can do as many pixel-density variants as you like.

While this is cool and useful, x descriptors only account for a small percentage of responsive images usage. Why? They only let browsers adapt based on one thing: display pixel-density. A lot of times, though, our responsive images are on responsive layouts, and the image’s layout size is shrinking and stretching right along with the viewport. In those situations, the browser needs to make decisions based on two things: the pixel-density of the screen, and the layout size of the image. That’s where w descriptors and the sizes attribute come in, which we’ll look at in the next section.

Using srcset / w + sizes

This is the good stuff. This accounts for around 85% of responsive images usage on the web. We’re still serving the same image at multiple sizes, only we’re giving the browser more information so that it can adapt based on both pixel-density and layout size.

<img    alt="A baby smiling with a yellow headband."   srcset="     baby-s.jpg  300w,     baby-m.jpg  600w,     baby-l.jpg  1200w,     baby-xl.jpg 2000w   "   sizes="70vmin" >

We’re still providing multiple copies of the same image and letting the browser pick the most appropriate one. But instead of labeling them with a pixel density (x) we’re labelling them with their resource width, using w descriptors. So if baby-s.jpg is 300×450, we label it as 300w.

Using srcset with width (w) descriptors like this means that it will need to be paired with the sizes attribute so that the browser will know how large of a space the image will be displaying in. Without this information, browsers can’t make smart choices.

Demo

Creating accurate sizes

Creating sizes attributes can get tricky. The sizes attribute describes the width that the image will display within the layout of your specific site, meaning it is closely tied to your CSS. The width that images render at is layout-dependent rather than just viewport dependent!

Let’s take a look at a fairly simple layout with three breakpoints. Here’s a video demonstrating this:

Demo

The breakpoints are expressed with media queries in CSS:

body {   margin: 2rem;   font: 500 125% system-ui, sans-serif; } .page-wrap {   display: grid;   gap: 1rem;   grid-template-columns: 1fr 200px;   grid-template-areas:     "header header"     "main aside"     "footer footer"; }  @media (max-width: 700px) {   .page-wrap {     grid-template-columns: 100%;     grid-template-areas:       "header"       "main"       "aside"       "footer";   } } @media (max-width: 500px) {   body {     margin: 0;   } }

The image is sized differently at each breakpoint. Here’s a breakdown of all of the bits and pieces that affect the image’s layout width at the largest breakpoint (when the viewport is wider than 700px):

The image is as wide as 100vw minus all that explicitly sized margin, padding, column widths, and gap.
  • At the largest size: there is 9rem of explicit spacing, so the image is calc(100vw - 9rem - 200px) wide. If that column used a fr unit instead of 200px, we’d kinda be screwed here.
  • At the medium size: the sidebar is dropped below, so there is less spacing to consider. Still, we can do calc(100vw - 6rem) to account for the margins and padding.
  • At the smallest size: the body margin is removed, so just calc(100vw - 2rem) will do the trick.

Phew! To be honest, I found that a little challenging to think out, and made a bunch of mistakes as I was creating this. In the end, I had this:

<img    ...     sizes="     (max-width: 500px) calc(100vw - 2rem),      (max-width: 700px) calc(100vw - 6rem),     calc(100vw - 9rem - 200px)   " />

A sizes attribute that gives the browser the width of the image across all three breakpoints, factoring in the layout grid, and all of the surrounding gap, margin, and padding that end up impacting the image’s width.

Now wait! Drumroll! 🥁🥁🥁That’s still wrong. I don’t understand why exactly, because to me that looks like it 100% describes what is happening in the CSS layout. But it’s wrong because Martin Auswöger’s RespImageLint says so. Running that tool over the isolated demo reports no problems except the fact that the sizes attribute is wrong for some viewport sizes, and should be:

<img   ...   sizes="     (min-width: 2420px) 2000px,      (min-width: 720px) calc(94.76vw - 274px),      (min-width: 520px) calc(100vw - 96px),      calc(100vw - 32px)   " >

I don’t know how that’s calculated and it’s entirely unmaintainable by hand, but, it’s accurate. Martin’s tool programmatically resizes the page a bunch and writes out a sizes attribute that describes the actual, observed width of the image over a wide range of viewport sizes. It’s computers, doing math, so it’s right. So, if you want a super-accurate sizes attribute, I’d recommend just putting a wrong one on at first, running this tool, and copying out the correct one.

For an even deeper dive into all this, check out Eric Portis’ w descriptors and sizes: Under the hood.

Being more chill about sizes

Another option is use the Horseshoes & Hand Grenades Method™ of sizes (or, in other words, close counts).

For example, sizes="96vw" says, “This image is going to be pretty big on the page — almost the full width — but there will always be a little padding around the edges, so not quite. Or sizes="(min-width: 1000px) 33vw, 96vw" says, “This image is in a three-column layout on large screens and close to full-width otherwise.” Practicality-wise, this can be a sane solution.

You might find that some automated responsive image solutions, which have no way of knowing your layout, make a guess — something like sizes="(max-width: 1000px) 100vw, 1000px". This is just saying, “Hey we don’t really know much about this layout, but we’re gonna take a stab and say, worst case, the image is full-width, and let’s hope it never renders larger than 1000px”.

Abstracting sizes

I’m sure you can imagine how easy it is to not only get sizes wrong, but also have it become wrong over time as layouts change on your site. It may be smart for you to abstract it using a templating language or content filter so that you can change the value across all of your images more easily.

I’m essentially talking about setting a sizes value in a variable once, and using that variable in a bunch of different <img> elements across your site. Native HTML doesn’t offer that, but any back end language does; for instance, PHP constants, Rails config variables, the React context API used for a global state variable, or variables within a templating language like Liquid can all be used to abstract sizes.

<?php   // Somewhere global   $ my_sizes = ""; ?>  <img   srcset=""   src=""   alt=""   sizes="<?php echo $ my_sizes; ?>" />

“Browser’s choice”

Now that we have a sizes attribute in place, the browser knows what size (or close to it) the image will render at and can work its magic. That is, it can do some math that factors in the pixel density of the screen, and the size that the image will render at, then pick the most appropriately-sized image.

The math is fairly straightforward at first. Say you’re about to show an image that is 40vw wide on a viewport that is 1200px wide, on a 2x pixel-density screen. The perfect image would be 960 pixels wide, so the browser is going to look for the closest thing it’s got. The browser will always calculate a target size that it would prefer based on the viewport and pixel-density situations, and what it knows from sizes, and compare that target to what it’s got to pick from in srcset. How browsers do the picking, though, can get a little weird.

A browser might factor more things into this equation if it chooses to. For example, it could consider the user’s current network speeds, or whether or not the user has flipped on some sort of “data saver” preference. I’m not sure if any browsers actually do this sort of thing, but they are free to if they wish as that’s how the spec was written. What some browsers sometimes choose to do is pull from cache. If the math shows they should be using a 300px image, but they already have a 600px in local cache, they will just use that. Smart. Room for this sort of thing is a strength of the srcset/sizes syntax. It’s also why you always use different sizes of the same image, within srcset: you’ve got no way to know which image is going to be selected. It’s the browser’s choice.

This is weird. Doesn’t the browser already know this stuff?

You might be thinking, “Uhm why do I have to tell the browser how big the image will render, doesn’t it know that?” Well, it does, but only after it’s downloaded your HTML and CSS and laid everything out. The sizes attribute is about speed. It gives the browser enough information to make a smart choice as soon as it sees your <img>.

<img   data-sizes="auto"   data-srcset="     responsive-image1.jpg 300w,     responsive-image2.jpg 600w,     responsive-image3.jpg 900w"   class="lazyload"  />

Now you might be thinking, “But what about lazy-loaded images?” (as in, by the time a lazy-loaded image is requested, layout’s already been done and the browser already knows the image’s render size). Well, good thinking! Alexander Farkas’ lazysizes library writes out sizes attributes automatically on lazyload, and there’s an ongoing discussion about how to do auto-sizes for lazy-loaded images, natively.

sizes can be bigger than the viewport

Quick note on sizes. Say you have an effect on your site so that an image “zooms in” when it’s clicked. Maybe it expands to fill the whole viewport, or maybe it zooms even more, so that you can see more detail. In the past, we might have had to swap out the src on click in order to switch to a higher-res version. But now, assuming a higher-res source is already in the srcset, you can just change the sizes attribute to something huge, like 200vw or 300vw, and the browser should download the super-high-res source automatically for you. Here’s an article by Scott Jehl on this technique.

↩️ Back to top


Using <picture>

Hopefully, we’ve beaten it into the ground that <img srcset="" sizes="" alt=""> is for serving differently-sized versions of the same image. The <picture> syntax can do that too, but the difference here is that the browser must respect the rules that you set. That’s useful when you want to change more than just the resolution of the loaded image to fit the user’s situation. This intentional changing of the image is usually called “art direction.”

Art Direction

<picture>   <source      srcset="baby-zoomed-out.jpg"     media="(min-width: 1000px)"   />   <source      srcset="baby.jpg"     media="(min-width: 600px)"   />   <img      src="baby-zoomed-in.jpg"      alt="Baby Sleeping"   /> </picture>

This code block is an example of what it might look like to have three stages of an “art directed” image.

  • On large screens, show a zoomed-out photo.
  • On medium screens, show that same photo, zoomed in a bit.
  • On small screens, zoom in even more.

The browser must respect our media queries and will swap images at our exact breakpoints. That way, we can be absolutely sure that nobody on a small screen will see a tiny, zoomed-out image, which might not have the same impact as one of the zoomed-in versions.

Here’s a demo, written in Pug to abstract out some of the repetitive nature of <picture>.

Art direction can do a lot more than just cropping

Although cropping and zooming like this is the most common form of art direction by far, you can do a lot more with it. For instance, you can:

Sky’s the limit, really.

Combining source and srcset

Because <source> also uses the srcset syntax, they can be combined. This means that you can still reap the performance benefits of srcset even while swapping out visually-different images with <source>. It gets pretty verbose though!

<picture>   <source      srcset="       baby-zoomed-out-2x.jpg 2x,       baby-zoomed-out.jpg     "     media="(min-width: 1000px)"   />   <source      srcset="       baby-2x.jpg 2x,       baby.jpg     "     media="(min-width: 600px)"   />   <img      srcset="       baby-zoomed-out-2x.jpg 2x     "     src="baby-zoomed-out.jpg"     alt="Baby Sleeping"   /> </picture>

The more variations you create and the more resized versions you create per variation, the more verbose this code has to get.

Fallbacks for modern image formats

The <picture> element is uniquely suited to being able to handle “fallbacks.” That is, images in cutting-edge formats that not all browsers might be able to handle, with alternative formats for browsers that can’t load the preferred, fancy one. For example, let’s say you want to use an image in the WebP format. It’s a pretty great image format, often being the most performant choice, and it’s supported everywhere that the <picture> element is, except Safari. You can handle that situation yourself, like:

<picture>   <source srcset="party.webp">   <img src="party.jpg" alt="A huge party with cakes."> </picture>

This succeeds in serving a WebP image to browsers that support it, and falls back to a JPEG image, which is definitely supported by all browsers.

Here’s an example of a photograph (of me) at the exact same size where the WebP version is about 10% (!!!) of the size of the JPEG.

How do you create a WebP image? Well, it’s more of a pain in the butt than you’d like it to be, that’s for sure. There are online converters, command line tools, and some modern design software, like Sketch, helps you export in that format. My preference is to use an image hosting CDN service that automatically sends images in the perfect format for the requesting browser, which makes all this unnecessary (because you can just use img/srcset).

WebP isn’t the only player like this. Safari doesn’t support WebP, but does support a format called JPG 2000 which has some advantages over JPEG. Internet Explorer 11 happens to support an image format called JPEG-XR which has different advantages. So to hit all three, that could look like:

<picture>   <source srcset="/images/cereal-box.webp" type="image/webp" />   <source srcset="/images/cereal-box.jp2" type="image/jp2" />   <img src="/images/cereal-box.jxr" type="image/vnd.ms-photo" /> </picture>

This syntax (borrowed form a blog post by Josh Comeau) supports all three of the “next-gen” image formats in one go. IE 11 doesn’t support the <picture> syntax, but it doesn’t matter because it will get the <img> fallback which is in the JPEG-XR format it understands.

Estelle Weyl also covered this idea in a 2016 blog post on image optimization.

↩️ Back to top


Where do you get the differently-sized images?

You can make them yourself. Heck, even the free Preview app on my Mac can resize an image and “Save As.”

The Mac Preview app resizing an image, which is something that literally any image editing application (including Photoshop, Affinity Designer, Acorn, etc.) can also do. Plus, they often help by exporting the variations all at once.

But that’s work. It’s more likely that the creation of variations of these images is automated somehow (see the section below) or you use a service that allows you to create variations just by manipulating the URL to the image. That’s a super common feature of any image hosting/image CDN service. To name a few:

Not only do these services offer on-the-fly image resizing, they also often offer additional stuff, like cropping, filtering, adding text, and all kinds of useful features, not to mention serving assets efficiently from a CDN and automatically in next-gen formats. That makes them a really strong choice for just about any website, I’d say.

Here’s Glen Maddern in a really great screencast talking about how useful Image CDNs can be:

Design software is booming more aware that we often need multiple copies of images. The exporting interface from Figma is pretty nice, where any given selection can be exported. It allows multiple exports at once (in different sizes and formats) and remembers what you dod the last time you exported.

Exporting in Figma

Automated responsive images

The syntax of responsive images is complex to the point that doing it by hand is often out of the question. I’d highly recommend automating and abstracting as much of this away as possible. Fortunately, a lot of tooling that helps you build websites knows this and includes some sort of support for it. I think that’s great because that’s what software should be doing for us, particularly when it is something that is entirely programmatic and can be done better by code than by humans. Here are some examples…

  • Cloudinary has this responsive breakpoints tool including an API for generating the perfect breakpoints.
  • WordPress generates multiple versions of images and outputs in the responsive images syntax by default.
  • Gatsby has a grab-bag of plugins for transforming and implementing images on your site. You ultimately implement them with gatsby-image, which is a whole fancy thing for implementing responsive images and other image loading optimizations. Speaking of React, it has component abstractions like “An Almost Ideal React Image Component” that also does cool stuff.
  • Nicolas Hoizey’s Images Responsiver Node module (and it’s Eleventy plugin) makes a ton of smart markup choices for you, and pairs nicely with a CDN that can handle the on-the-fly resizing bits.
  • These are just a few examples! Literally anything you can do to make this process easier or automatic is worth doing.
Here’s me inspecting an image in a WordPress blog post and seeing a beefy srcset with a healthy amount of pre-generated size options and a sizes attribute tailored to this theme.
A landing page for gatsby-image explaining all of the additional image loading stuff it can do.

I’m sure there are many more CMSs and other software products that help automate away the complexities of creating the responsive images syntax. While I love that all this syntax exists, I find it all entirely too cumbersome to author by hand. Still, I think it’s worth knowing all this syntax so that we can build our own abstractions, or check in on the abstractions we’re using to make sure they are doing things correctly.

Related concepts

  • The object-fit property in CSS controls how an image will behave in its own box. For example, an image will normally “squish” if you change the dimensions to something different than its natural aspect ratio, but object-fit can be used to crop it or contain it instead.
  • The object-position property in CSS allows you to nudge an image around within its box.

What about responsive images in CSS with background images?

We’ve covered exactly this before. The trick is to use @media queries to change the background-image source. For example:

.img {   background-image: url(small.jpg); } @media    (min-width: 468px),   (-webkit-min-device-pixel-ratio: 2),    (min-resolution: 192dpi) {   .img {     background-image: url(large.jpg);   } }

With this CSS syntax, depending on the browser conditions, the browser will only download one of the two images, which achieves the same performance goal that the responsive images syntax in HTML does. If it helps, think of the above as the CSS equivalent of the <picture> syntax: the browser must follow your rules and display what matches.

If you’re looking to let the browser choose the best option, like srcset/sizes, but in CSS, the solution is ultimately going to be the image-set() function. There’s two problems with image-set(), today, though:

  • Support for it isn’t there yet. Safari’s implementation leads the pack, but image-set() has been prefixed in Chrome for eight years, and it’s not there at all in Firefox.
  • Even the spec itself seems behind the times. For example, it only supports x descriptors (no w, yet).

Best to just use media queries for now.

Do you need to polyfill?

I’m pretty meh on pollyfilling any of this right this moment. There is a great polyfill though, called Picturefill, which will buy you full IE 9-11 support if you need that. Remember, though, that none of this stuff breaks to the point of not displaying any image at all in non-supporting browsers, assuming you have an <img src="" alt=""> in there somewhere. If you make the (fairly safe) assumption that IE 11 is running on a low-pixel-density desktop display, you can make your image sources reflect that by default and build out from there.

Other important image considerations

  • Optimizing quality: The point of responsive images is loading the smallest, most impactful resource that you can. You can’t achieve that without effectively compressing your image. You’re aiming for a “sweet spot” for every image, between looking good and being light. I like to let image hosting services solve this problem for me, but Etsy has a really great writeup of what they’ve been able to accomplish with infrastructure that they built themselves.
  • Serving from CDNs: Speaking of image hosting services, speed comes in many forms. Fast servers that are geographically close to the user are an important speed factor as well.
  • Caching: What’s better than loading less data over the network? Loading no data at all! That’s what HTTP caching is for. Using the Cache-Control header, you can tell the browser to hang on to images so that if the same image is needed again, the browser doesn’t have to go over the network to get it, which is a massive performance boost for repeat viewings.
  • Lazy loading: This is another way to avoid loading images entirely. Lazy loading means waiting to download an image until it is in or near the viewport. So, for example, an image way far down the page won’t load if the user never scrolls there.

Other good resources

(That I haven’t linked up in the post already!)

Browser Support

This is for srcset/sizes, but it’s the same for <picture>.

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

Chrome Firefox IE Edge Safari
38 38 No 16 9

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
81 68 81 9.0-9.2

The post A Guide to the Responsive Images Syntax in HTML appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]