pseudo elements – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Tue, 20 Dec 2022 17:42:26 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 pseudo elements – CSS-Tricks https://css-tricks.com 32 32 45537868 Creating Animated, Clickable Cards With the :has() Relational Pseudo Class https://css-tricks.com/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/ https://css-tricks.com/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/#comments Tue, 25 Oct 2022 14:15:17 +0000 https://css-tricks.com/?p=374690 The CSS :has() pseudo class is rolling out in many browsers with Chrome and Safari already fully supporting it. It’s often referred to it as “the parent selector” — as in, we can select style a parent element from a …


Creating Animated, Clickable Cards With the :has() Relational Pseudo Class originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The CSS :has() pseudo class is rolling out in many browsers with Chrome and Safari already fully supporting it. It’s often referred to it as “the parent selector” — as in, we can select style a parent element from a child selector — but there is so much more that :has() can help us solve. One of those things is re-inventing the clickable card pattern many of us love to use from time to time.

We’ll take a look at how :has() can help us handle linked cards, but first…

What is this :has() pseudo class?

There is already a bunch of great posts floating around that do an excellent job explaining what :has() is and what it’s used for, but it’s still new enough that we ought to say a few words about it here as well.

:has() is a relational pseudo class that’s part of the W3C Selectors Level 4 working draft. That’s what the parentheses are all about: matching elements that are related to — or, more accurately, contain — certain child elements.

/* Matches an article element that contains an image element */
article:has(img) { }

/* Matches an article element with an image contained immediately within it */
article:has(> img) { }

So, you can see why we might want to call it a “parent” selector. But we can also combine it with other functional pseudo classes to get more specific. Say we want to style articles that do not contain any images. We can combine the relational powers of :has() with the negation powers of :not() to do that:

/* Matches an article without images  */
article:not(:has(img)) { }

But that’s just the start of how we can combine powers to do more with :has(). Before we turn specifically to solving the clickable card conundrum, let’s look at a few ways we currently approach them without using :has().

How we currently handle clickable cards

There are three main approaches on how people create a fully clickable card these days and to fully understand the power of this pseudo class, it’s nice to have a bit of a round-up.

This approach is something used quite frequently. I never use this approach but I created a quick demo to demonstrate it:

There are a lot of concerns here, especially when it comes to accessibility. When users navigate your website using the rotor function, they will hear the full text inside of that <a> element — the heading, the text, and the link. Someone might not want to sit through all that. We can do better. Since HTML5, we can nest block elements inside of an <a> element. But it never feels right to me, especially for this reason.

Pros:

  • Quick to implement
  • Semantically correct

Cons:

  • Accessibility concerns
  • Text not selectable
  • A lot of hassle to overwrite styles that you used on your default links

The JavaScript method

Using JavaScript, we can attach a link to our card instead of writing it in the markup. I found this great CodePen demo by costdev who also made the card text selectable in the process:

This approach has a lot of benefits. Our links are accessible on focus and we can even select text. But there are some drawbacks when it comes to styling. If we want to animate those cards, for example, we would have to add :hover styles on our main .card wrapper instead of the link itself. We also would not benefit from the animations when the links are in focus from keyboard tabbing.

Pros:

  • Can be made perfectly accessible
  • Ability to select text

Cons:

  • Requires JavaScript
  • Right clicking not possible (although could be fixed with some extra scripting)
  • Will require a lot of styling on the card itself which would not work when focussing the link

The ::after selector approach

This method requires us to set the card with relative positioning, then set absolute positioning on the link’s ::after pseudo selector of a link. This doesn’t require any JavaScript and is pretty easy to implement:

There are a few drawbacks here, especially when it comes to selecting text. Unless you provide a higher z-index on your card-body, you won’t be able to select text but if you do, be warned that clicking the text will not activate your link. Whether or not you want selectable text is up to you. I think it can be a UX issue, but it depends on the use-case. The text is still accessible to screen readers but my main problem with the method is the lack of animation possibilities.

Pros:

  • Easy to implement
  • Accessible link without bloated text
  • Works on hover and focus

Cons:

  • Text is not selectable
  • You can only animate the link as this is the element you’re hovering.

A new approach: Using ::after with :has()

Now that we’ve established the existing approaches for clickable cards, I want to show how introducing :has() to the mix solves most of those shortcomings.

In fact, let’s base this approach on the last one we looked at using ::after on the link element. We can actually use :has() there to overcome that approach’s animation constraints.

Let’s start with the markup:

<article>
  <figure>
    <img src="cat.webp" alt="Fluffy gray and white tabby kitten snuggled up in a ball." />
  </figure>
  <div clas="article-body">
    <h2>Some Heading</h2>
    <p>Curabitur convallis ac quam vitae laoreet. Nulla mauris ante, euismod sed lacus sit amet, congue bibendum eros. Etiam mattis lobortis porta. Vestibulum ultrices iaculis enim imperdiet egestas.</p>
    <a href="#">
      Read more
       <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 20 20" fill="currentColor">
         <path fill-rule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" />
       </svg>
    </a>
  </div>
</article>

I will be keeping things as simple as possible by targeting elements in the CSS instead of classes.

For this demo, we’re going to add an image zoom and shadow to the card on hover, and animate the link with an arrow popping up and while changing the link’s text color. To make this easy, we’re going to add some custom properties scoped on our card. Here’s the basic styling:

/* The card element */
article {
  --img-scale: 1.001;
  --title-color: black;
  --link-icon-translate: -20px;
  --link-icon-opacity: 0;

  position: relative;
  border-radius: 16px;
  box-shadow: none;
  background: #fff;
  transform-origin: center;
  transition: all 0.4s ease-in-out;
  overflow: hidden;
}
/* The link's ::after pseudo */
article a::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline: 0;
  cursor: pointer;
}

Great! We added an initial scale for the image (--img-scale: 1.001), the initial color of the card heading (--title-color: black) and some extra properties we will use to make our arrow pop out of the link. We’ve also set an empty state of the box-shadow declaration in order to animate it later . This sets up what we need for the clickable card right now, so let’s add some resets and styling to it by adding those custom properties to the elements we want to animate:

article h2 {
  margin: 0 0 18px 0;
  font-family: "Bebas Neue", cursive;
  font-size: 1.9rem;
  letter-spacing: 0.06em;
  color: var(--title-color);
  transition: color 0.3s ease-out;
}
article figure {
  margin: 0;
  padding: 0;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}
article img {
  max-width: 100%;
  transform-origin: center;
  transform: scale(var(--img-scale));
  transition: transform 0.4s ease-in-out;
}
article a {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: #28666e;
}
article a:focus {
  outline: 1px dotted #28666e;
}
article a .icon {
  min-width: 24px;
  width: 24px;
  height: 24px;
  margin-left: 5px;
  transform: translateX(var(--link-icon-translate));
  opacity: var(--link-icon-opacity);
  transition: all 0.3s;
}

.article-body {
  padding: 24px;
}

Let’s be kind to people and also add a screen reader class hidden behind the link:

.sr-only:not(:focus):not(:active) {
  clip: rect(0 0 0 0); 
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap; 
  width: 1px;
}

Our card is starting to look pretty sweet. It’s time to add a bit of magic to it. With the :has() pseudo class, we can now check if our link is hovered or focused, then update our custom properties and add a box-shadow. With this little chunk of CSS our card really comes to life:

/* Matches an article element that contains a hover or focus state */
article:has(:hover, :focus) {
  --img-scale: 1.1;
  --title-color: #28666e;
  --link-icon-translate: 0;
  --link-icon-opacity: 1;

  box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px;
}

See what’s up there? Now we get the updated styles if any child element in the card is hovered or focused. And even though the link element is the only thing that can contain a hover or focus state in the ::after clickable card approach, we can use that to match the parent element and apply the transitions.

And there you have it. Just another powerful use case for the :has() selector. Not only can we match a parent element by declaring other elements as arguments, but we can match also use pseudos to match and style parents as well.

Pros:

  • Accessible
  • Animatable
  • No JavaScript needed
  • Uses :hover on the correct element

Cons:

  • Text is not easily selectable.
  • Browser support is limited to Chrome and Safari (it’s supported in Firefox behind a flag).

Here is a demo using this technique. You might notice an extra wrapper around the card, but that’s just me playing around with container queries, which is just one of those other fantastic things rolling out in all major browsers.

Got some other examples you wish to share? Other solutions or ideas are more than welcome in the comment section.


Creating Animated, Clickable Cards With the :has() Relational Pseudo Class originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/feed/ 10 374690
Single Element Loaders: The Bars https://css-tricks.com/single-element-loaders-the-bars/ https://css-tricks.com/single-element-loaders-the-bars/#comments Fri, 24 Jun 2022 20:00:29 +0000 https://css-tricks.com/?p=366526 We’ve looked at spinners. We’ve looked at dots. Now we’re going to tackle another common pattern for loaders: bars. And we’re going to do the same thing in this third article of the series as we have the others …


Single Element Loaders: The Bars originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We’ve looked at spinners. We’ve looked at dots. Now we’re going to tackle another common pattern for loaders: bars. And we’re going to do the same thing in this third article of the series as we have the others by making it with only one element and with flexible CSS that makes it easy to create variations.

Let’s start with not one, not two, but 20 examples of bar loaders.

What?! Are you going to detail each one of them? That’s too much for an article!

It might seem like that at first glance! But all of them rely on the same code structure and we only update a few values to create variations. That’s all the power of CSS. We don’t learn how to create one loader, but we learn different techniques that allow us to create as much loader as we want using merely the same code structure.

Let’s make some bars!

We start by defining the dimensions for them using width (or height) with aspect-ratio to maintain proportion:

.bars {
  width: 45px;
  aspect-ratio: 1;
}

We sort of “fake” three bars with a linear gradient on the background — very similar to how we created dot loaders in Part 2 of this series.

.bars {
  width: 45px;
  aspect-ratio: 1;
  --c: no-repeat linear-gradient(#000 0 0); /* we define the color here */
  background: 
    var(--c) 0%   50%,
    var(--c) 50%  50%,
    var(--c) 100% 50%;
  background-size: 20% 100%; /* 20% * (3 bars + 2 spaces) = 100% */
}

The above code will give us the following result:

Like the other articles in this series, we are going to deal with a lot of background trickery. So, if you ever feel like we’re jumping around too fast or feel you need a little more detail, please do check those out. You can also read my Stack Overflow answer where I give a detailed explanation on how all this works.

Animating the bars

We either animate the element’s size or position to create the bar loader. Let’s animate the size by defining the following animation keyframes:

@keyframes load {
  0%   { background-size: 20% 100%, 20% 100%, 20% 100%; }  /* 1 */
  33%  { background-size: 20% 10% , 20% 100%, 20% 100%; }  /* 2 */
  50%  { background-size: 20% 100%, 20% 10% , 20% 100%; }  /* 3 */
  66%  { background-size: 20% 100%, 20% 100%, 20% 10%;  }  /* 4 */
  100% { background-size: 20% 100%, 20% 100%, 20% 100%; }  /* 5 */
}

See what’s happening there? Between 0% and 100%, the animation changes the background-size of the element’s background gradient. Each keyframe sets three background sizes (one for each gradient).

And here’s what we get:

Can you start to imagine all the possible variations we can get by playing with different animation configurations for the sizes or the positions?

Let’s fix the size to 20% 50% and update the positions this time:

.loader {
  width: 45px;
  aspect-ratio: .75;
  --c: no-repeat linear-gradient(#000 0 0);
  background: 
    var(--c),
    var(--c),
    var(--c);
  background-size: 20% 50%;
  animation: load 1s infinite linear;
}
@keyframes load {
  0%   { background-position: 0% 100%, 50% 100%, 100% 100%; } /* 1 */
  20%  { background-position: 0% 50% , 50% 100%, 100% 100%; } /* 2 */
  40%  { background-position: 0% 0%  , 50% 50% , 100% 100%; } /* 3 */
  60%  { background-position: 0% 100%, 50% 0%  , 100% 50%;  } /* 4 */
  80%  { background-position: 0% 100%, 50% 100%, 100% 0%;   } /* 5 */ 
  100% { background-position: 0% 100%, 50% 100%, 100% 100%; } /* 6 */
}

…which gets us another loader!

You’ve probably got the trick by now. All you need is to define a timeline that you translate into a keyframe. By animating the size, the position — or both! — there’s an infinite number of loader possibilities at our fingertips.

And once we get comfortable with such a technique we can go further and use a more complex gradient to create even more loaders.

Expect for the last two examples in that demo, all of the bar loaders use the same underlying markup and styles and different combinations of animations. Open the code and try to visualize each frame independently; you’ll see how relatively trivial it is to make dozens — if not hundreds — of variations.

Getting fancy

Did you remember the mask trick we did with the dot loaders in the second article of this series? We can do the same here!

If we apply all the above logic inside the mask property we can use any background configuration to add a fancy coloration to our loaders.

Let’s take one demo and update it:

All I did is updating all the background-* with mask-* and I added a gradient coloration. As simple as that and yet we get another cool loader.

So there is no difference between the dots and the bars?

No difference! I wrote two different articles to cover as many examples as possible but in both, I am relying on the same techniques:

  1. Gradients to create the shapes (dots or bars or maybe something else)
  2. Animating background-size and/or background-position to create the loader animation
  3. Adding mask to add a touch of colors

Rounding the bars

Let’s try something different this time where we can round the edges of our bars.

Using one element and its ::before and ::after pseudos, we define three identical bars:

.loader {
  --s: 100px; /* control the size */

  display: grid;
  place-items: center;
  place-content: center;
  margin: 0 calc(var(--s) / 2); /* 50px */
}
.loader::before,
.loader::after {
  content: "";
  grid-area: 1/1;
}
.loader,
.loader::before,
.loader::after {
  height: var(--s);
  width: calc(var(--s) / 5); /* 20px */
  border-radius: var(--s);
  transform: translate(calc(var(--_i, 0) * 200%));
}
.loader::before { --_i: -1; }
.loader::after { --_i:  1; }

That gives us three bars, this time without relying on a linear gradient:

Now the trick is to fill in those bars with a lovely gradient. To simulate a continuous gradient, we need to play with background properties. In the above figure, the green area defines the area covered by the loader. That area should be the size of the gradient and, if we do the math, it’s equal to multiplying both sides labeled S in the diagram, or background-size: var(--s) var(--s).

Since our elements are individually placed, we need to update the position of the gradient inside each one to make sure all of them overlap. This way, we’re simulating one continuous gradient even though it’s really three of them.

For the main element (placed at the center), the background needs to be at the center. We use the following:

.loader {
  /* etc. */
  background: linear-gradient() 50% / var(--s) var(--s);
}

For the pseudo-element on the left, we need the background on the left

.loader::before {
  /* etc. */
  background: linear-gradient() 0% / var(--s) var(--s);
}

And for the pseudo on the right, the background needs to be positioned to the right:

.loader::after {
  background: linear-gradient() 100% / var(--s) var(--s);
}

Using the same CSS variable, --_i, that we used for the translate, we can write the code like this:

.loader {
  --s: 100px; /* control the size */
  --c: linear-gradient(/* etc. */); /* control the coloration */

  display: grid;
  place-items: center;
  place-content: center;
}
.loader::before,
.loader::after{
  content: "";
  grid-area: 1/1;
}
.loader,
.loader::before,
.loader::after{
  height: var(--s);
  width: calc(var(--s) / 5);
  border-radius: var(--s);
  background: var(--c) calc(50% + var(--_i, 0) * 50%) / var(--s) var(--s);
  transform: translate(calc(var(--_i, 0) * 200%));
}
.loader::before { --_i: -1; }
.loader::after  { --_i:  1; }

Now, all we have to do is to animate the height and add some delays! Here are three examples where all that’s different are the colors and sizes:

Wrapping up

I hope so far you are feeling super encouraged by all the powers you have to make complex-looking loading animations. All we need is one element, either gradients or pseudos to draw the bars, then some keyframes to move things around. That’s the entire recipe for getting an endless number of possibilities, so go out and starting cooking up some neat stuff!

Until the next article, I will leave you with a funny collection of loaders where I am combining the dots and the bars!


Single Element Loaders: The Bars originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/single-element-loaders-the-bars/feed/ 8 366526
Conditionally Styling Selected Elements in a Grid Container https://css-tricks.com/conditionally-styling-selected-elements-in-a-grid-container/ https://css-tricks.com/conditionally-styling-selected-elements-in-a-grid-container/#comments Wed, 15 Jun 2022 14:15:50 +0000 https://css-tricks.com/?p=366252 Calendars, shopping carts, galleries, file explorers, and online libraries are some situations where selectable items are shown in grids (i.e. square lattices). You know, even those security checks that ask you to select all images with crosswalks or whatever.

🧐…


Conditionally Styling Selected Elements in a Grid Container originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Calendars, shopping carts, galleries, file explorers, and online libraries are some situations where selectable items are shown in grids (i.e. square lattices). You know, even those security checks that ask you to select all images with crosswalks or whatever.

🧐

I found a neat way to display selectable options in a grid. No, not recreating that reCAPTCHA, but simply being able to select multiple items. And when two or more adjoining items are selected, we can use clever :nth-of-type combinators, pseudo elements, and the :checked pseudo-class to style them in a way where they look grouped together.

The whole idea of combinators and pseudos to get the rounded checkboxes came from a previous article I wrote. It was a simple single-column design:

This time, however, the rounding effect is applied to elements along both the vertical and horizontal axes on a grid. You don’t have to have read my last article on checkbox styling for this since I’m going to cover everything you need to know here. But if you’re interested in a slimmed down take on what we’re doing in this article, then that one is worth checking out.

Before we start…

It’ll be useful for you to take note of a few things. For example, I’m using static HTML and CSS in my demo for the sake of simplicity. Depending on your application you might have to generate the grid and the items in it dynamically. I’m leaving out practical checks for accessibility in order to focus on the effect, but you would definitely want to consider that sort of thing in a production environment.

Also, I’m using CSS Grid for the layout. I’d recommend the same but, of course, it’s only a personal preference and your mileage may vary. For me, using grid allows me to easily use sibling-selectors to target an item’s ::before and ::after pseudos.

Hence, whatever layout standard you might want to use in your application, make sure the pseudos can still be targeted in CSS and ensure the layout stays in tact across different browsers and screens.

Let’s get started now

As you may have noticed in the earlier demo, checking and unchecking a checkbox element modifies the design of the boxes, depending on the selection state of the other checkboxes around it. This is possible because I styled each box using the pseudo-elements of its adjacent elements instead of its own element.

The following figure shows how the ::before pseudo-elements of boxes in each column (except the first column) overlap the boxes to their left, and how the ::after pseudo-elements of boxes in each row (except the first row) overlap the boxes above.

Two grids of checkboxes showing the placement of before and after pseudos.

Here’s the base code

The markup is pretty straightforward:

<main>
  <input type=checkbox> 
  <input type=checkbox> 
  <input type=checkbox>
  <!-- more boxes -->
</main>

There’s a little more going on in the initial CSS. But, first, the grid itself:

/* The grid */
main {
  display: grid;
  grid:  repeat(5, 60px) / repeat(4, 85px);
  align-items: center;
  justify-items: center;
  margin: 0;
}

That’s a grid of five rows and four columns that contain checkboxes. I decided to wipe out the default appearance of the checkboxes, then give them my own light gray background and super rounded borders:

/* all checkboxes */
input {
  -webkit-appearance: none;
  appearance: none;
  background: #ddd;
  border-radius: 20px;
  cursor: pointer;
  display: grid;
  height: 40px;
  width: 60px;
  margin: 0;
}

Notice, too, that the checkboxes themselves are grids. That’s key for placing their ::before and ::after pseudo-elements. Speaking of which, let’s do that now:

/* pseudo-elements except for the first column and first row */
input:not(:nth-of-type(4n+1))::before,
input:nth-of-type(n+5)::after {
  content: '';        
  border-radius: 20px;
  grid-area: 1 / 1;
  pointer-events: none;
}

We’re only selecting the pseudo-elements of checkboxes that are not in the first column or the first row of the grid. input:not(:nth-of-type(4n+1)) starts at the first checkbox, then selects the ::before of every fourth item from there. But notice we’re saying :not(), so really what we’re doing is skipping the ::before pseudo-element of every fourth checkbox, starting at the first. Then we’re applying styles to the ::after pseudo of every checkbox from the fifth one.

Now we can style both the ::before and ::after pseudos for each checkbox that is not in the first column or row of the grid, so that they are moved left or up, respectively, hiding them by default.

/* pseudo-elements other than the first column */
input:not(:nth-of-type(4n+1))::before { 
  transform: translatex(-85px);
}

/* pseudo-elements other than the first row */
input:nth-of-type(n+5)::after {
 transform: translatey(-60px); 
}

Styling the :checked state

Now comes styling the checkboxes when they are in a :checked state. First, let’s give them a color, say a limegreen background:

input:checked { background: limegreen; }

A checked box should be able to re-style all of its adjacent checked boxes. In other words, if we select the eleventh checkbox in the grid, we should also be able to style the boxes surrounding it at the top, bottom, left, and right.

A four-by-five grid of squares numbered one through 20. 11 is selected and 7, 10, 12, and 15 are highlighted.

This is done by targeting the correct pseudo-elements. How do we do that? Well, it depends on the actual number of columns in the grid. Here’s the CSS if two adjacent boxes are checked in a 5⨉4 grid:

/* a checked box's right borders (if the element to its right is checked) */
input:not(:nth-of-type(4n)):checked + input:checked::before { 
  border-top-right-radius: 0; 
  border-bottom-right-radius: 0; 
  background: limegreen;
}
/* a checked box's bottom borders (if the element below is checked) */
input:nth-last-of-type(n+5):checked + * + * + * + input:checked::after {
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
  background: limegreen;
}
/* a checked box's adjacent (right side) checked box's left borders */
input:not(:nth-of-type(4n)):checked + input:checked + input::before {         
  border-top-left-radius: 0; 
  border-bottom-left-radius: 0; 
  background: limegreen;
}
/* a checked box's adjacent (below) checked box's top borders */
input:not(:nth-of-type(4n)):checked + * + * + * +  input:checked + input::before { 
  border-top-left-radius: 0; 
  border-top-right-radius: 0; 
  background: limegreen;
}

If you prefer you can generate the above code dynamically. However, a typical grid, say an image gallery, the number of columns will be small and likely a fixed number of items, whereas the rows might keep increasing. Especially if designed for mobile screens. That’s why this approach is still an efficient way to go. If for some reason your application happens to have limited rows and expanding columns, then consider rotating the grid sideways because, with a stream of items, CSS Grid arranges them left-to-right and top-to-bottom (i.e. row by row).

We also need to add styling for the last checkboxes in the grid — they’re not all covered by pseudo-elements as they are the last items in each axis.

/* a checked box's (in last column) left borders */
input:nth-of-type(4n-1):checked + input:checked {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
/* a checked box's (in last column) adjacent (below) checked box's top borders */
input:nth-of-type(4n):checked + * + * + * + input:checked {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

Those are some tricky selectors! The first one…

input:nth-of-type(4n-1):checked + input:checked

…is basically saying this:

A checked <input> element next to a checked <input> in the second last column.

And the nth-of-type is calculated like this:

4(0) - 1 = no match
4(1) - 1 = 3rd item
4(2) - 1 = 7th item
4(3) - 1 = 11th item
etc.

So, we’re starting at the third checkbox and selecting every fourth one from there. And if a checkbox in that sequence is checked, then we style the checkboxes adjacent, too, if they are also checked.

And this line:

input:nth-of-type(4n):checked + * + * + * + input:checked

Is saying this:

An <input> element provided that is checked, is directly adjacent to an element, which is directly adjacent to another element, which is also directly adjacent to another element, which, in turn, is directly adjacent to an <input> element that is in a checked state.

What that means is we’re selecting every fourth checkbox that is checked. And if a checkbox in that sequence is checked, then we style the next fourth checkbox from that checkbox if it, too, is checked.

Putting it to use

What we just looked at is the general principle and logic behind the design. Again, how useful it is in your application will depend on the grid design.

I used rounded borders, but you can try other shapes or even experiment with background effects (Temani has you covered for ideas). Now that you know how the formula works, the rest is totally up to your imagination.

Here’s an instance of how it might look in a simple calendar:

Again, this is merely a rough prototype using static markup. And, there would be lots and lots of accessibility considerations to consider in a calendar feature.


That’s a wrap! Pretty neat, right? I mean, there’s nothing exactly “new” about what’s happening. But it’s a good example of selecting things in CSS. If we have a handle on more advanced selecting techniques that use combinators and pseudos, then our styling powers can reach far beyond the styling one item — as we saw, we can conditionally style items based on the state of another element.


Conditionally Styling Selected Elements in a Grid Container originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/conditionally-styling-selected-elements-in-a-grid-container/feed/ 3 366252
Single Element Loaders: The Spinner https://css-tricks.com/single-element-loaders-the-spinner/ https://css-tricks.com/single-element-loaders-the-spinner/#comments Fri, 10 Jun 2022 14:26:06 +0000 https://css-tricks.com/?p=366266 Making CSS-only loaders is one of my favorite tasks. It’s always satisfying to look at those infinite animations. And, of course, there are lots of techniques and approaches to make them — no need to look further than CodePen to …


Single Element Loaders: The Spinner originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Making CSS-only loaders is one of my favorite tasks. It’s always satisfying to look at those infinite animations. And, of course, there are lots of techniques and approaches to make them — no need to look further than CodePen to see just how many. In this article, though, we will see how to make a single element loader writing as little code as possible.

I have made a collection of more than 500 single div loaders and in this four-part series, I am going to share the tricks I used to create many of them. We will cover a huge number of examples, showing how small adjustments can lead to fun variations, and how little code we need to write to make it all happen!

Single-Element Loaders series:

  1. Single Element Loaders: The Spinner — you are here
  2. Single Element Loaders: The Dots
  3. Single Element Loaders: The Bars
  4. Single Element Loaders: Going 3D

For this first article, we are going to create a one of the more common loader patterns: spinning bars:

Here’s the approach

A trivial implementation for this loader is to create one element for each bar wrapped inside a parent element (for nine total elements), then play with opacity and transform to get the spinning effect.

My implementation, though, requires only one element:

<div class="loader"></div>

…and 10 CSS declarations:

.loader {
  width: 150px; /* control the size */
  aspect-ratio: 1;
  display: grid;
  mask: conic-gradient(from 22deg, #0003, #000);
  animation: load 1s steps(8) infinite;
}
.loader,
.loader:before {
  --_g: linear-gradient(#17177c 0 0) 50%; /* update the color here */
  background: 
    var(--_g)/34% 8%  space no-repeat,
    var(--_g)/8%  34% no-repeat space;
}
.loader:before {
  content: "";
  transform: rotate(45deg);
}
@keyframes load {
  to { transform: rotate(1turn); }
}

Let’s break that down

At first glance, the code may look strange but you will see that it’s more simple than what you might think. The first step is to define the dimension of the element. In our case, it’s a 150px square. We can put aspect-ratio to use so the element stays square no matter what.

.loader {
  width: 150px; /* control the size */
  aspect-ratio: 1; /* make height equal to width */
}

When building CSS loaders, I always try to have one value for controlling the overall size. In this case, it’s the width and all the calculations we cover will refer to that value. This allows me to change a single value to control the loader. It’s always important to be able to easily adjust the size of our loaders without the need to adjust a lot of additional values.

Next, we will use gradients to create the bars. This is the trickiest part! Let’s use one gradient to create two bars like the below:

background: linear-gradient(#17177c 0 0) 50%/34% 8% space no-repeat;
Showing a space between two gradient lines for a single element loader.

Our gradient is defined with one color and two color stops. The result is a solid color with no fading or transitions. The size is equal to 34% wide and 8% tall. It’s also placed in the center (50%). The trick is the use of the keyword value space — this duplicates the gradient, giving us two total bars.

From the specification:

The image is repeated as often as will fit within the background positioning area without being clipped and then the images are spaced out to fill the area. The first and last images touch the edges of the area.

I am using a width equal to 34% which means we cannot have more than two bars (3*34% is greater than 100%) but with two bars we will have empty spaces (100% - 2 * 34% = 32%). That space is placed in the center between the two bars. In other words, we use a width for the gradient that is between 33% and 50% to make sure we have at least two bars with a little bit of space between them. The value space is what correctly places them for us.

We do the same and make a second similar gradient to get two more bars at the top and bottom, which give us a background property value of:

background: 
 linear-gradient(#17177c 0 0) 50%/34% 8%  space no-repeat,
 linear-gradient(#17177c 0 0) 50%/8%  34% no-repeat space;

We can optimize that using a CSS variable to avoid repetition:

--_g: linear-gradient(#17177c 0 0) 50%; /* update the color here */
background: 
 var(--_g)/34% 8%  space no-repeat,
 var(--_g)/8%  34% no-repeat space;

So, now we have four bars and, thanks to CSS variables, we can write the color value once which makes it easy to update later (like we did with the size of the loader).

To create the remaining bars, let’s tap into the .loader element and its ::before pseudo-element to get four more bars for a grand total of eight in all.

.loader {
  width: 150px; /* control the size */
  aspect-ratio: 1;
  display: grid;
}
.loader,
.loader::before {
  --_g: linear-gradient(#17177c 0 0) 50%; /* update the color here */
  background: 
    var(--_g)/34% 8%  space no-repeat,
    var(--_g)/8%  34% no-repeat space;
}
.loader::before {
  content: "";
  transform: rotate(45deg);
}

Note the use of display: grid. This allows us to rely on the grid’s default stretch alignment to make the pseudo-element cover the whole area of its parent; thus there’s no need to specify a dimension on it — another trick that reduces the code and avoid us to deal with a lot of values!

Now let’s rotate the pseudo-element by 45deg to position the remaining bars. Hover the following demo to see the trick:

Setting opacity

What we’re trying to do is create the impression that there is one bar that leaves a trail of fading bars behind it as it travels a circular path. What we need now is to play with the transparency of our bars to make that trail, which we are going to do with CSS mask combined with a conic-gradient as follows:

mask: conic-gradient(from 22deg,#0003,#000);

To better see the trick, let’s apply this to a full-colored box:

The transparency of the red color is gradually increasing clockwise. We apply this to our loader and we have the bars with different opacity:

Radial gradient plus, spinner bars equals spinner bars with gradients.

In reality, each bar appears to fade because it’s masked by a gradient and falls between two semi-transparent colors. It’s hardly noticeable when this runs, so it’s sort of like being able to say that all the bars have the same color with a different level of opacity.

The rotation

Let’s apply a rotation animation to get our loader. Note, that we need a stepped animation and not a continuous one that’s why I am using steps(8). 8 is nothing but the number of the bars, so that value can be changed depending on how many bars are in use.

.loader {
  animation: load 3s steps(8) infinite;
}

/* Same as before: */
@keyframes load {
  to { transform: rotate(1turn) }
}

That’s it! We have our loader with only one element and a few lines of CSS. We can easily control its size and color by adjusting one value.

Since we only used the ::before pseudo-element, we can add four more bars by using ::after to end with 12 bars in total and almost the same code:

We update the rotation of our pseudo-elements to consider 30deg and 60deg instead of 45deg while using an twelve-step animation, rather than eight. I also decreased the height to 5% instead of 8% to make the bars a little thinner.

Notice, too, that we have grid-area: 1/1 on the pseudo-elements. This allows us to place them in the same area as one another, stacked on top of each other.

Guess what? We can reach for the same loader using another implementation:

Can you figure out the logic behind the code? Here is a hint: the opacity is no longer handled with a CSS mask but inside the gradient and is also using the opacity property.

Why not dots instead?

We can totally do that:

If you check the code, you will see that we’re now working with a radial gradient instead of a linear one. Otherwise, the concept is exactly the same where the mask creates the impression of opacity, but we made the shapes as circles instead of lines.

Below is a figure to illustrate the new gradient configuration:

Showing placement of dots in the single-element loader.

If you’re using Safari, note that the demo may be buggy. That’s because Safari currently lacks support for the at syntax in radial gradients. But we can reconfigure the gradient a bit to overcome that:

.loader,
.loader:before,
.loader:after {
  background:
    radial-gradient(
      circle closest-side,
      currentColor 90%,
      #0000 98%
    ) 
    50% -150%/20% 80% repeat-y,
    radial-gradient(
      circle closest-side,
      currentColor 90%,
      #0000 98%
    ) 
    -150% 50%/80% 20% repeat-x;
}

More loader examples

Here is another idea for a spinner loader similar to the previous one.

For this one, I am only relying on background and mask to create the shape (no pseudo-elements needed). I am also defining the configuration with CSS variables to be able to create a lot of variations from the same code — another example of just the powers of CSS variables. I wrote another article about this technique if you want to more details.

Note that some browsers still rely on a -webkit- prefix for mask-composite with its own set of values, and will not display the spinner in the demo. Here is a way to do it without mast-composite for more browser support.

I have another one for you:

For this one, I am using a background-color to control the color, and use mask and mask-composite to create the final shape:

Different steps for applying a master to a element in the shape of a circle.

Before we end, here are some more spinning loaders I made a while back. I am relying on different techniques but still using gradients, masks, pseudo-element, etc. It could be a good exercise to figure out the logic of each one and learn new tricks at the same time. This said, if you have any question about them, the comment section is down below.

Wrapping up

See, there’s so much we can do in CSS with nothing but a single div, a couple of gradients, pseudo-elements, variables. It seems like we created a whole bunch of different spinning loaders, but they’re all basically the same thing with slight modifications.

This is only the the beginning. In this series, we will be looking at more ideas and advanced concepts for creating CSS loaders.

Single-Element Loaders series:

  1. Single Element Loaders: The Spinner — you are here
  2. Single Element Loaders: The Dots
  3. Single Element Loaders: The Bars
  4. Single Element Loaders: Going 3D

Single Element Loaders: The Spinner originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/single-element-loaders-the-spinner/feed/ 4 366266
CSS Pseudo Commas https://css-tricks.com/css-pseudo-commas/ https://css-tricks.com/css-pseudo-commas/#comments Mon, 30 Aug 2021 21:20:00 +0000 https://css-tricks.com/?p=350670 A bonafide CSS trick if there ever was one! @ShadowShahriar created a CodePen demo that uses pseudo-elements to place commas between list items that are displayed inline, and the result is a natural-looking complete sentence with proper punctuation.

CodePen Embed…


CSS Pseudo Commas originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A bonafide CSS trick if there ever was one! @ShadowShahriar created a CodePen demo that uses pseudo-elements to place commas between list items that are displayed inline, and the result is a natural-looking complete sentence with proper punctuation.

How it works

The trick? First, it’s to make an unordered list an inline element with no markers or spacing:

ul {
  padding: 0;
  margin: 0;
  display: inline;
  list-style-type: none;
}

Next, we display list items inline so they flow naturally as text in a sentence:

li {
  display: inline;
}

Then we add commas between list items by selecting their ::after pseudo-element, and setting it’s content property with a comma (,) value.

li::after{
  content: var(--separator);
}

Oh, but wait! What about the ol’ Oxford comma? Use :nth-last-of-type() to select the second-to-last list item, and set its ::after pseudo-element’s content property to ", and" before the last list item.

li:nth-last-of-type(2)::after{
  content: ", and ";
}

We’re not done. @ShadowShahriar considers an edge case where there are only two items. All we need is to display an “and” between those two items, so:

li:first-of-type:nth-last-of-type(2)::after {
  content: " and ";
}

I had to look that up on Selectors Explained to make sure I was reading it correctly. That’s saying:

The after pseudo-element

… of a <li> element provided it is the first of its type in its parent and the nth of its type from the end (formula) in its parent.

What a mouthful! The final touch is a period at the end of the list:

li:last-of-type::after {
  content: ".";
}

Using custom properties

We just looked at an abridged version of the actual code. @ShadowShahriar does a nice thing by setting a comma and the “and” as custom properties:

ul {
  --separator: ",";
  --connector: "and";

  padding: 0;
  margin: 0;
  display: inline;
  list-style-type: none;
}

That way, when we can swap those out for other ways to separate list items later. Nice touch.


This caught my eye not only for its clever use of pseudo-element trickery, but also for its simplicity. It’s using tried and true CSS principles in a way that supports semantic HTML — no extra classes, elements, or even JavaScript to help manipulate things. It almost makes me wonder if HTML could use some sort of inline list element (<il> anyone???) to help support sentences convey list items without breaking out of a paragraph.

To Shared LinkPermalink on CSS-Tricks


CSS Pseudo Commas originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-pseudo-commas/feed/ 8 350670
The CSS :has Selector (and 4+ Examples) https://css-tricks.com/the-css-has-selector/ https://css-tricks.com/the-css-has-selector/#comments Wed, 17 Mar 2021 18:40:16 +0000 https://css-tricks.com/?p=336424 The CSS :has selector helps you select elements when they contain other elements that match the selector you pass into :has().


The CSS :has Selector (and 4+ Examples) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The CSS :has selector helps you select elements that contain elements that match the selector you pass into the :has() function. It’s essentially a “parent” selector, although far more useful than just that. For example, imagine being able to select all <div>s but only when they contain a <p>. That’s what we can do:

div:has(p) {
  background: red;
}
/*
  <div> <!-- selected! -->
     <p></p>
  <div>

  <div></div> <!-- not selected -->
  <div> <!-- not selected -->
    <section></section>
  </div>
*/

Although it’s not supported in any browser as I write, it has now dropped in Safari Technical Preview 137, so it’s starting to happen!

Pretty darn handy right! Here’s another example. Say you want space after your headers. Of course! A bit of margin-block-end on your h2 should do it. But… what if there is a subtitle? Now we can select a parent on the condition a subtitle is present and adjust the spacing.

h2,
.subtitle {
  margin: 0 0 1.5rem 0;
}
.header-group:has(h2):has(.subtitle) h2 {
  margin: 0 0 0.2rem 0; /* reduce spacing on header, because subtitle will handle it */
}

/*
  <div class="header-group">
    <h2>Blog Post Title</h2> <!-- main spacing applied here -->
  </div>

  <div class="header-group">
    <h2>Blog Post Title</h2>
    <div class="subtitle"> <!-- main spacing applied here -->
      This is a subtitle
    </div>
  </div>
*/
The CSS :has selector being helpful in spacing headers with subtitles (or not).
On the left, the main spacing happens after the h2, on the right, the main spacing happens after the subtitle.

The way I think about :has is this: it’s a parent selector pseudo-class. That is CSS-speak for “it lets you change the parent element if it has a child or another element that follows it.” This might feel weird! It might break your mental model of how CSS works. This is how I’m used to thinking about CSS:

.parent .child {
  color: red;
}

You can only style down, from parent to child, but never back up the tree. :has completely changes this because up until now there have been no parent selectors in CSS and there are some good reasons why. Because of the way in which browsers parse HTML and CSS, selecting the parent if certain conditions are met could lead to all sorts of performance concerns.

If I sit down and think about all the ways I might use :has today, I sort of get a headache. It would open up this pandora’s box of opportunities that have never been possible with CSS alone.

Another example: let’s say we want to only apply styles to links that have images in them:

a:has(> img) {
  border: 20px solid white;
}

This would be helpful from time to time. I can also see :has being used for conditionally adding margin and padding to elements depending on their content. That would be neat.

The :has selector is part of the CSS Selectors Level 4 specification which is the same spec that has the extremely useful :not pseudo-class.


You could argue that the CSS :has selector is more powerful than just a “parent” selector, which is exactly what Bramus has done! Like in the subheadings example above, you aren’t necessarily ultimately selecting the parent, you might select the parent in a has-condition, but then ultimately select a child element from there.

/*  Matches <figure> elements that have a <figcaption> as a child element */
figure:has(figcaption) { … }

/* Matches <img> elements that is a child of a <figure> that contains a <figcaption> child element */
figure:has(figcaption) img { … }

There you can quickly see that the second selector is selecting a child <img>, not just the parent of the <figcaption>.

Selector List

You can chain it:

article:has(h2):has(ul) {

}

Or give it a selector list:

article:has(h2, ul) {

}

And the list is forgiving: The list is no longer “forgiving” after the W3C adopted a resolution in December 2020 in response to a reported issue. So, if the selector list contains even one invalid argument, the entire list is ignored:

/* 👎 */
article:has(h2, ul, ::-blahdeath) {
  /* ::blahdeath is invalid, making the entire selector invalid. */
}

A workaround is to nest a more forgiving selector in there, such as :is() or :where():

/* 👍 */
article:has(:where(h2, ul, ::-blahdeath)) {
  /* :where is a forgiving selector, making this valid. */
}

Testing for Support

@supports(selector(:has(p))) {
  /* Supported! */
}

The :not() selector is part of the same spec…

Unlike :has, :not does have pretty decent browser support and I used it for the first time the other day:

ul li:not(:first-of-type) {
  color: red;
}

That’s great I also love how gosh darn readable it is; you don’t ever have to have seen this line of code to understand what it does.

Another way you can use :not is for margins:

ul li:not(:last-of-type) {
  margin-bottom: 20px;
}

So every element that is not the last item gets a margin. This is useful if you have a bunch of elements in a card, like this:

… and also :is() and :where()

CSS Selectors Level 4 is also the same spec that has the :is selector that can be used like this today in a lot of browsers:

:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
  color: #BADA55;
}

/* ... which would be the equivalent of: */
section h1, section h2, section h3, section h4, section h5, section h6, 
article h1, article h2, article h3, article h4, article h5, article h6, 
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6, 
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
  color: #BADA55;
}

More Info

So that’s it! :has should be quite useful to use soon, and its cousins :is and :not can be fabulously helpful already and that’s only a tiny glimpse — just three CSS pseudo-classes — that are available in this new spec.


The CSS :has Selector (and 4+ Examples) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-css-has-selector/feed/ 29 336424
Bold on Hover… Without the Layout Shift https://css-tricks.com/bold-on-hover-without-the-layout-shift/ https://css-tricks.com/bold-on-hover-without-the-layout-shift/#comments Mon, 27 Jul 2020 14:36:49 +0000 https://css-tricks.com/?p=317525 When you change the font-weight of a font, the text will typically cause a bit of a layout shift. That’s because bold text is often larger and takes up more space. Sometimes that doesn’t matter, like a vertical stack of …


Bold on Hover… Without the Layout Shift originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
When you change the font-weight of a font, the text will typically cause a bit of a layout shift. That’s because bold text is often larger and takes up more space. Sometimes that doesn’t matter, like a vertical stack of links where the wider/bolder text doesn’t push anything anyway. Sometimes it does matter, like a horizontal row where the wider/bolder text pushes other elements away a smidge.

Ryan Mulligan demonstrates:

Ryan’s technique is very clever. Each item in the list has a pseudo-element on it with the exact text in the link. That pseudo-element is visually hidden, but pre-bolded and still occupies width. So when the actual link text is bolded, it won’t take up any additional width.

It also sorta depends on how you’re doing the layout. Here, if I force four columns with CSS grid and text that doesn’t really challenge the width, the bolding doesn’t affect the layout either:

But if I were to, say, let those links flow into automatic columns, we would have the shifting problem.


Bold on Hover… Without the Layout Shift originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/bold-on-hover-without-the-layout-shift/feed/ 25 317525
Pseudo-elements in the Web Animations API https://css-tricks.com/pseudo-elements-in-the-web-animations-api/ Wed, 13 May 2020 23:13:35 +0000 https://css-tricks.com/?p=308234 To use the Web Animations API (e.g. el.animate()) you need a reference to a DOM element to target. So, how do you use it on pseudo-elements, which don’t really offer a direct reference? Dan Wilson covers a (newish?) part …


Pseudo-elements in the Web Animations API originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
To use the Web Animations API (e.g. el.animate()) you need a reference to a DOM element to target. So, how do you use it on pseudo-elements, which don’t really offer a direct reference? Dan Wilson covers a (newish?) part of the API itself:

const logo = document.getElementById('logo');

logo.animate({ opacity: [0, 1] }, {
  duration: 100,
  pseudoElement: '::after'
});

I noticed in Dan’s article that ::marker is supported. I was just playing with that recently while doing our List Style Recipes page. I figured I’d give it a spin by testing the WAAPI and @keyframes on both a ::marker and and ::after element:

At first, I confused myself because it seemed like the WAAPI wasn’t working on ::after, but Dan reminded me that when using a transform, the element can’t be display: inline. Instead, I made it inline-block and it worked fine. However, I did uncover that @keyframes don’t seem to work on ::marker elements in Firefox — hopefully they’ll fix that (and we get Chrome and Safari support for ::marker ASAP).

To Shared LinkPermalink on CSS-Tricks


Pseudo-elements in the Web Animations API originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
308234
A Little Reminder That Pseudo Elements are Children, Kinda. https://css-tricks.com/a-little-reminder-that-pseudo-elements-are-children-kinda/ https://css-tricks.com/a-little-reminder-that-pseudo-elements-are-children-kinda/#comments Mon, 08 Jul 2019 20:45:05 +0000 http://css-tricks.com/?p=292093 Here’s a container with some child elements:

<div class="container">
  <div>item</div>
  <div>item</div>
  <div>item</div>
</div>

If I do:

.container::before {
  content: "x"
}

I’m essentially doing:

<div class="container">
  [[[ ::before psuedo-element here ]]]
  <div>item</div>
  <div>item</div>
  <div>item</div>
</div>

Which will behave just like …


A Little Reminder That Pseudo Elements are Children, Kinda. originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Here’s a container with some child elements:

<div class="container">
  <div>item</div>
  <div>item</div>
  <div>item</div>
</div>

If I do:

.container::before {
  content: "x"
}

I’m essentially doing:

<div class="container">
  [[[ ::before psuedo-element here ]]]
  <div>item</div>
  <div>item</div>
  <div>item</div>
</div>

Which will behave just like a child element mostly. One tricky thing is that no selector selects it other than the one you used to create it (or a similar selector that is literally a ::before or ::after that ends up in the same place).

To illustrate, say I set up that container to be a 2×3 grid and make each item a kind of pillbox design:

.container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 0.5rem;
}

.container > * {
  background: darkgray;
  border-radius: 4px;
  padding: 0.5rem;
}

Without the pseudo-element, that would be like this:

Six items in a clean two-by-two grid

If I add that pseudo-element selector as above, I’d get this:

Six items in a two-by-two grid, but with a seventh item at the beginning, pushing elements over by one

It makes sense, but it can also come as a surprise. Pseudo-elements are often decorative (they should pretty much only be decorative), so having it participate in a content grid just feels weird.

Notice that the .container > * selector didn’t pick it up and make it darkgray because you can’t select a pseudo-element that way. That’s another minor gotcha.

In my day-to-day, I find pseudo-elements are typically absolutely-positioned to do something decorative — so, if you had:

.container::before {
  content: "";
  position: absolute;
  /* Do something decorative */
}

…you probably wouldn’t even notice. Technically, the pseudo-element is still a child, so it’s still in there doing its thing, but isn’t participating in the grid. This isn’t unique to CSS Grid either. For instance, you’ll find by using flexbox that your pseudo-element becomes a flex item. You’re free to float your pseudo-element or do any other sort of layout with it as well.

DevTools makes it fairly clear that it is in the DOM like a child element:

DevTools with a ::before element selected

There are a couple more gotchas!

One is :nth-child(). You’d think that if pseduo-elements are actually children, they would effect :nth-child() calculations, but they don’t. That means doing something like this:

.container > :nth-child(2) {
  background: red;
}

…is going to select the same element whether or not there is a ::before pseudo-element or not. The same is true for ::after and :nth-last-child and friends. That’s why I put “kinda” in the title. If pseudo-elements were exactly like child elements, they would affect these selectors.

Another gotcha is that you can’t select a pseudo-element in JavaScript like you could a regular child element. document.querySelector(".container::before"); is going to return null. If the reason you are trying to get your hands on the pseudo-element in JavaScript is to see its styles, you can do that with a little CSSOM magic:

const styles = window.getComputedStyle(
  document.querySelector('.container'),
  '::before'
);
console.log(styles.content); // "x"
console.log(styles.color); // rgb(255, 0, 0)
console.log(styles.getPropertyValue('color'); // rgb(255, 0, 0)

Have you run into any gotchas with pseudo-elements?


A Little Reminder That Pseudo Elements are Children, Kinda. originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-little-reminder-that-pseudo-elements-are-children-kinda/feed/ 1 292093
Valid CSS Content https://css-tricks.com/valid-css-content/ https://css-tricks.com/valid-css-content/#comments Thu, 11 Oct 2018 14:03:19 +0000 http://css-tricks.com/?p=276996 There is a content property in CSS that’s made to use in tandem with the ::before and ::after pseudo elements. It injects content into the element.

Here’s an example:

<div 
  data-done="&#x2705;"
  class="email">
    chriscoyier@gmail.com
</div>
.email::before {
  content: attr(data-done) " Email: 


Valid CSS Content originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There is a content property in CSS that’s made to use in tandem with the ::before and ::after pseudo elements. It injects content into the element.

Here’s an example:

<div 
  data-done="&#x2705;"
  class="email">
    chriscoyier@gmail.com
</div>
.email::before {
  content: attr(data-done) " Email: "; /* This gets inserted before the email address */
}

The property generally takes anything you drop in there. However, there are some invalid values it won’t accept. I heard from someone recently who was confused by this, so I had a little play with it myself and learned a few things.

This works fine:

/* Valid */
::after {
  content: "1";
}

…but this does not:

/* Invalid, not a string */
::after {
  content: 1;
}

I’m not entirely sure why, but I imagine it’s because 1 is a unit-less number (i.e. 1 vs. 1px) and not a string. You can’t trick it either! I tried to be clever like this:

/* Invalid, no tricks */
::after {
  content: "" 1;
}

You can output numbers from attributes though, as you might suspect:

<div data-price="4">Coffee</div>
/* This "works" */
div::after {
  content: " $" attr(data-price);
}

But of course, you’d never use generated content for important information like a price, right?! (Please don’t. It’s not very accessible, nor is the text selectable.)

Even though you can get and display that number, it’s just a string. You can’t really do anything with it.

<div data-price="4" data-sale-modifier="0.9">Coffee</div>
/* Not gonna happen */
div::after {
  content: " $" 
    calc(attr(data-price) * attr(data-sale-modifier));
}

You can’t use numbers, period:

/* Nope */
::after {
  content: calc(2 + 2);
}

Heads up! Don’t try concatenating strings like you might in PHP or JavaScript:

/* These will break */
::after {
  content: "1" . "2" . "3";
  content: "1" + "2" + "3";

  /* Use spaces */
  content: "1" "2" "3";
  /* Or nothing */
  content: "1 2 3";
  /* The type of quote (single or double) doesn't matter, but content not coming back from attr() does need to be quoted. */
}

There is a thing in the spec for converting attributes into the actual type rather than treating them all like strings…

<wood length="12" />
wood {
  width: attr(length em); /* or other values like "number", "px", or "url" */
}

…but I’m fairly sure that isn’t working anywhere yet. Plus, it doesn’t help us with pseudo elements anyway, since strings already work and numbers don’t.

The person who reached out to me over email was specifically confused why they were unable to use calc() on content. I’m not sure I can help you do math in this situation, but it’s worth knowing that pseudo elements can be counters, and those counters can do their own limited form of math. For example, here’s a counter that starts at 12 and increments by -2 for each element at that level in the DOM.

See the Pen Backwards Double Countdown by Chris Coyier (@chriscoyier) on CodePen.

The only other thing we haven’t mentioned here is that a pseudo element can be an image. For example:

p:before {
  content: url(image.jpg);
}

…but it’s weirdly limited. You can’t even resize the image. ¯\_(ツ)_/¯

Much more common is using an empty string for the value (content: "";) which can do things like clear floats but also be positioned, sized and have a background of its own.


Valid CSS Content originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/valid-css-content/feed/ 11 276996
One Invalid Pseudo Selector Equals an Entire Ignored Selector https://css-tricks.com/one-invalid-pseudo-selector-equals-an-entire-ignored-selector/ https://css-tricks.com/one-invalid-pseudo-selector-equals-an-entire-ignored-selector/#comments Fri, 05 Oct 2018 13:45:01 +0000 http://css-tricks.com/?p=276762 Perhaps you know this one: if any part of a selector is invalid, it invalidates the whole selector. For example:

div, span::butt {
  background: red;
}

Even though div is a perfectly valid selector, span:butt is not, thus the entire …


One Invalid Pseudo Selector Equals an Entire Ignored Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Perhaps you know this one: if any part of a selector is invalid, it invalidates the whole selector. For example:

div, span::butt {
  background: red;
}

Even though div is a perfectly valid selector, span:butt is not, thus the entire selector is invalidated — neither divs nor span::butt elements on the page will have a red background.

Normally that’s not a terribly huge problem. It may even be even useful, depending on the situation. But there are plenty of situations where it has kind of been a pain in the, uh, :butt.

Here’s a classic:

::selection {
  background: lightblue;
}

For a long time, Firefox didn’t understand that selector, and required a vendor prefix (::-moz-selection) to get the same effect. (This is no longer the case in Firefox 62+, but you take the point.)

In other words, this wasn’t possible:

/* would break for everyone */
::selection, ::-moz-selection {
  background: lightblue;
}

That would break for browsers that understood ::selection and break for Firefox that only understood ::-moz-selection. It made it ripe territory for a preprocessor @mixin, that’s for sure.

Here’s another zinger.

/* For navigation with submenus */
ul.submenu {
  display: none;
}

ul.menu li:hover ul.submenu,
ul.menu li:focus ul.submenu,
ul.menu li:focus-within ul.submenu {
  display: block;
}

/* Oh no! We've broken all menu functionality in IE 11, 
   because it doesn't know what `:focus-within` is so it
   throws out the entire selector */

This behavior is annoying enough that browsers have apparently fixed it going forward. In a conversation with Estelle Weyl, I learned that this is being changed. She wrote in the MDN docs:

Generally, if there is an invalid pseudo-element or pseudo-class within in a chain or group of selectors, the whole selector list is invalid. If a pseudo-element (but not pseudo-class) has a -webkit- prefix, As of Firefox 63, Blink, Webkit and Gecko browsers assume it is valid, not invalidating the selector list.

This isn’t for any selector; it’s specifically for pseudo-elements. That is, double colons (::).

Here’s a test:

I’d call that a positive change.


One Invalid Pseudo Selector Equals an Entire Ignored Selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/one-invalid-pseudo-selector-equals-an-entire-ignored-selector/feed/ 5 276762
Old Timey Terminal Styling https://css-tricks.com/old-timey-terminal-styling/ https://css-tricks.com/old-timey-terminal-styling/#comments Tue, 11 Sep 2018 15:32:16 +0000 http://css-tricks.com/?p=275830 I saw a little demo the other day called HTML5 Terminal. It has some functionality, but it seems like it’s just kinda for fun. That said, I loved the aesthetic of it! Blurry monospace type with visible monitor lines …


Old Timey Terminal Styling originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I saw a little demo the other day called HTML5 Terminal. It has some functionality, but it seems like it’s just kinda for fun. That said, I loved the aesthetic of it! Blurry monospace type with visible monitor lines and a glowing green background — nice!

Let’s re-create that, bit-by-bit.

A radial gradient is perfect for the glowing green background:

body {
  background-color: black;
  background-image: radial-gradient(
    rgba(0, 150, 0, 0.75), black 120%
  );
  height: 100vh;
}

I’m so used to using <pre><code> to display space-formatted text (like code), but you could say terminal text isn’t always code, so I like the use of <pre><output> here.

Might as well use a nice monospace font:

body {
  ...
  color: white;
  font: 1.3rem Inconsolata, monospace;
}

The text on the demo appears a bit blurry. We could go with a slight filter like filter: blur(0.6px);, but it seems blurring that way comes out either too blurry or not blurry enough. Let’s try using text-shadow instead:

body {
  ...
  text-shadow: 0 0 5px #C8C8C8;
}

Now on to those monitor lines! Ideally, they should sit on top of the text, so let’s use an ::after pseudo-element that’s absolutely positioned over the whole area and use a repeating linear gradient for the lines (because it’s always nice to avoid using images if we can):

body::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: repeating-linear-gradient(
    0deg,
    rgba(black, 0.15),
    rgba(black, 0.15) 1px,
    transparent 1px,
    transparent 2px
  );
}

You can see these faint black lines on white here:

And then over our original green burst background to complete the look:

It’s a nice touch to add a fun selection style and remove the blurring for clarity when selecting:

::selection {
  background: #0080FF;
  text-shadow: none;
}

Complete demo:

See the Pen Terminal Output by Chris Coyier (@chriscoyier) on CodePen.


Old Timey Terminal Styling originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/old-timey-terminal-styling/feed/ 16 275830
::before vs :before https://css-tricks.com/to-double-colon-or-not-do-double-colon/ https://css-tricks.com/to-double-colon-or-not-do-double-colon/#comments Thu, 23 Aug 2018 13:43:40 +0000 http://css-tricks.com/?p=275382 Note the double-colon ::before versus the single-colon :before. Which one is correct?

Technically, the correct answer is ::before. But that doesn’t mean you should automatically use it.

The situation is that:

  • double-colon selectors are pseudo-elements.
  • single-colon selectors are


::before vs :before originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Note the double-colon ::before versus the single-colon :before. Which one is correct?

Technically, the correct answer is ::before. But that doesn’t mean you should automatically use it.

The situation is that:

  • double-colon selectors are pseudo-elements.
  • single-colon selectors are pseudo-selectors.

::before is definitely a pseudo-element, so it should use the double colon.

The distinction between a pseudo-element and pseudo-selector is already confusing. Fortunately, ::after and ::before are fairly straightforward. They literally add something new to the page, an element.

But something like ::first-letter is also a pseudo-element. The way I reason that out in my brain is that it’s selecting a part of something in which there is no existing HTML element for. There is no <span> around that first letter you’re targeting, so that first letter is almost like a new element you’re adding on the page. That differs from pseudo-selectors which are selecting things that already exist, like the :nth-child(2) or whatever.

Even though ::before is a pseudo-element and a double-colon is the correct way to use pseudo-elements, should you?

There is an argument that perhaps you should use :before, which goes like this:

  1. Internet Explorer 8 and below only supported :before, not ::before
  2. All modern browsers support it both ways, since tons of sites use :before and browsers really value backwards compatibility.
  3. Hey it’s one less character as a bonus.

I’ve heard people say that they have a CSS linter that requires (or automates) them to be single-colon. Personally, I’m OK with people doing that. Seems fine. I’d value consistency over which way you choose to go.

On the flip side, there’s an argument for going with ::before that goes like this:

  1. Single-colon pseudo-elements were a mistake. There will never be any more pseudo-elements with a single-colon.
  2. If you have the distinction straight in your mind, might as well train your fingers to do it right.
  3. This is already confusing enough, so let’s just follow the correctly specced way.

I’ve got my linter set up to force me to do double-colons. I don’t support Internet Explorer 8 anyway and it feels good to be doing things the “right” way.


::before vs :before originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/to-double-colon-or-not-do-double-colon/feed/ 23 275382
Solved with CSS! Logical Styling Based on the Number of Given Elements https://css-tricks.com/solved-with-css-logical-styling-based-on-the-number-of-given-elements/ https://css-tricks.com/solved-with-css-logical-styling-based-on-the-number-of-given-elements/#comments Thu, 26 Jul 2018 14:06:17 +0000 http://css-tricks.com/?p=274090 Did you know that CSS is Turing complete? Did you know that you can use it to do some pretty serious logical styling? Well you can! You don’t have to set all of your logic-based styling rules in JavaScript, or even have to use JavaScript to set classes you are styling against. In many cases, CSS can handle that itself. I’m still discovering new CSS tricks everyday, and it just makes me love it even more.


Solved with CSS! Logical Styling Based on the Number of Given Elements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This post is the third in a series about the power of CSS.

Article Series:

  1. Colorizing SVG Backgrounds
  2. Dropdown Menus
  3. Logical Styling Based On the Number of Given Elements (this post)

Did you know that CSS is Turing complete? Did you know that you can use it to do some pretty serious logical styling? Well you can! You don’t have to set all of your logic-based styling rules in JavaScript, or even have to use JavaScript to set classes you are styling against. In many cases, CSS can handle that itself. I’m still discovering new CSS tricks everyday, and it just makes me love it even more.

This year, I started working at Bustle Digital Group. In media, as with a lot of products, the engineering team builds a platform that should support all use cases. Our CMS provides capabilities for authors and editors to create articles as well as curate pages, and control ad injection.

Unlike working with a static site, the engineering team doesn’t have full control over what data comes in from the user, so design decisions and governing rules must be made for a good user experience. Some of these scenarios we’ve faced in the digital media space have really inspired me to look into ways of using CSS to solve those UI challenges, and that’s when solutions involving this idea really came into my periphery.

So let’s take a look at some examples!

Example 1: Binary States

An often forgotten and very useful selector is the :empty pseudo selector. It allows you to style elements based on if they contain any content, or if they don’t. Hello empty states! Empty states are a great way to reach out to your users and show personality in your app, and you can inject that personality right from your CSS.

In this example, we have any list from a user. This could be posts the user has published (as an author), or bookmarked articles a user has saved (as an editor). The use cases are really endless here. Instead of injecting JavaScript, we can use pseudo elements to inject images, styles, and text:

Drawings illustrate conditionally applied styles based on no items (left) versus displayed items (right) in the list.

Our solution here is a mere three lines of code:

div:empty:after {
  content: 'oh no...';
}

You can also add a :before pseudo element to inject images or any other content you may want. Alternatively, the :not pseudo selector may be used in combination with :empty to create a :not(:empty) rule and style all elements which are not empty, and therefore do contain children.

See the Pen Empty States by Una Kravets (@una) on CodePen.

Note: This demo is for display purposes only. It is not advised to put content in pseudo elements for accessibility purposes. You can use the same technique of targeting :empty or :not(:empty) elements to apply styles to child elements that are more accessible to screen readers.

Advanced Numeric Selection

That was a nice soft ball example, but we can get much more complex than this binary choice of child elements in CSS, and to do this, we will use the :nth-child pseudo selector! CSS-Tricks has a great tool to help you test and play around with the :nth-child selection, and it can really come in handy as some of the examples will show you.

But before we get into those, how exactly does this work?

The meat of the code is this, with div standing in for any given sibling element, and x standing in for the number we are using to determine style breaks:

div:first-child:nth-last-child(n + x),
div:first-child:nth-last-child(n + x) ~ div

Using :nth-last-child instead of using :nth-child for selection allows us to start from the end of a series instead of from the beginning. When we select :nth-last-child(n + x), we are selecting the x value starting from the end. If x = 3 that would look like this:

Illustration of how :nth-last-child(3) selects the third item from the end of the list.

Now, if we want to count values of n + 3, we are selecting all items that match 3 or more than 3 from the end. Starting with n = 0 (which would mean 0 + 3, and the 4th item being the first from the end after 3). It looks like this:

Illustration of how :nth-last-child(n+3) selects all items that match 3 or more than 3 from the end.

This is a great start, but the idea here is to conditionally style all of the items based on how many exist. So we need to work with these conditions but select all of the items. Let’s start with selecting the first item. We need to make a condition to see if the entire selection qualifies for the styling, and then start with that first sibling:

Uh oh. We only have the first item selected at this point, and we want to select all of the items. Luckily, we can use the super handy adjacent sibling selector (~) for that!

Adjusting our last example to :first-child:last-child(n + 3) ~ * selects all items excluding the first like we want.

Well, now you can see all of the items that follow the first item are selected, but we’re missing the first one, so we need to use two selectors, and thus the final answer becomes:

Combining both of the previous two examples will select all items in the list.

Example 2: List Formatting

Say you want to list some credits at the end of an article. You’ve got some space to fill, and most articles have a small number of credits, but there are those exceptions that have a high production value and a lot of people involved in the making of them. We want to make sure both of these are good visual experiences, and can do that with CSS alone.

Here’s the plan: if there are four or fewer credits, list them in bullet format. Let them take up vertical space to fill the block appropriately. Once we have five or more credits listed, let’s turn that list into a horizontal format to not get too overwhelming for a reader. This is a small credits box after all!

Illustrations of a vertical unordered list (left) and a horizontal list separated by semicolons (right).

We can check out the number of elements we have available and style them as block elements until we hit our cap. At that point, we’ll switch to inline styling, and add a pseudo element to visually break up the data.

/* 5 or more items display next to each other */
li:first-child:nth-last-child(n + 5),
li:first-child:nth-last-child(n + 5) ~ li {
  display: inline;
}

/* Adds semicolon after each item except the last item */
li:first-child:nth-last-child(n + 5) ~ li::before {
  content: ';';
  margin: 0 0.5em 0 -0.75em;
}

:nth-first-child:nth-last-child(n + 5) allows us to state: “start with the first child and apply styling to that child and every sibling after it if the original child matches having five or more siblings.”” Is that confusing? Well, it works.

li:first-child:nth-last-child(n + 5) selects the first list item, and li:first-child:nth-last-child(n + 5) ~ li selects each list item following the initial one.

See the Pen vrQBMv by Una Kravets (@una) on CodePen.

Example 3: Conditional Carousel

Using this technique, let’s style a carousel to be responsive. At a large size, you want it to be centered in the middle of page when it has three items within it. But when it has enough items to fill the screen horizontally, let it be left-aligned for the user to swipe through it.

Illustrations of a carousel with three items (left) and more than three items (right).

What we can do here is stretch the elements to fit the screen unless we have too many elements and they would require an overflow. At that point, let’s go all-in on this overflow and really showcase the carousel capabilities by signaling scroll-ability with arrows and by increasing the margin between items. On top of that, let’s add a sticky arrow button to show that we can scroll through the elements and can tie JavaScript events to make the carousel scroll.

We can do the same thing as above in terms of the technique, but we will also use only the first-child to detect an arrow div and display it in the UI. The HTML would look like this:

<ul>
  <li>
    <div class="box">1</div>
  </li>
  <li>
    <div class="box">2</div>
  </li>
  ...
  <button class="arrow">——></button>
</ul>

It’s not ideal to have empty elements in the DOM, but work with me. It’s still a clever hack. We’ll style the .arrow button to be invisible to the DOM and to screen readers with visibility: hidden unless the conditions apply (in this case, if four or more items are present). At that point we’ll give it a visible display (display: block), style, and position it appropriately:

li:first-child:nth-last-child(n + 5) ~ .arrow {
  display: block;
  position: sticky;
  ...
}

See the Pen Box Alignment by Una Kravets (@una) on CodePen.

More Information!

In my research for this post, I discovered an excellent post by Heydon Pickering about this technique, called Quantity Queries, and another example by Lea Verou! In the comment thread of Heydon’s post, Paul Irish notes that this is a slower way of selecting elements, so maybe use it with caution.


Solved with CSS! Logical Styling Based on the Number of Given Elements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/solved-with-css-logical-styling-based-on-the-number-of-given-elements/feed/ 17 274090
Drawing Images with CSS Gradients https://css-tricks.com/drawing-images-with-css-gradients/ https://css-tricks.com/drawing-images-with-css-gradients/#comments Mon, 25 Jun 2018 14:10:56 +0000 http://css-tricks.com/?p=272923 What I mean by “CSS images” is images that are created using only HTML elements and CSS. They look as if they were SVGs drawn in Adobe Illustrator but they were made right in the browser. Some techniques I’ve seen …


Drawing Images with CSS Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
What I mean by “CSS images” is images that are created using only HTML elements and CSS. They look as if they were SVGs drawn in Adobe Illustrator but they were made right in the browser. Some techniques I’ve seen used are tinkering with border radii, box shadows, and sometimes clip-path. You can find a lot of great examples if you search daily css images” on CodePen. I drew some myself, including this Infinity Gauntlet, but in one element with only backgrounds and minimal use of other properties.

Let’s take a look at how you can create CSS images that way yourself.

The Method

Understanding the shorthand background syntax as well as how CSS gradients work is practically all you need to draw anything in one element. As a review, the arguments are as follows:

background: <'background-color'> || <image> || <position> [ / <size> ]? || <repeat> || <attachment> || <origin> || <clip>;

They can occur in any order except that there must be a / between the position and size. We must keep those two arguments in that order as well, or else we’ll get unexpected results. Not all of these need to be included, and for this purpose we won’t be using the color, repeat, attachment, origin, or clip arguments. This leaves us with image, size, and position. Since backgrounds repeat by default, however, we must place background-repeat: no-repeat; right under everything in background (if certain backgrounds ought to be repeat, we can use repeating-linear-gradient() and repeating-radial-gradient()). In that case, the skeleton CSS will be this:

.image {
  background: <image> <position> / <size>;
  background-repeat: no-repeat;
}

We can even use multiple sets of background arguments! Therefore, we can stack and separate them with commas like this:

.image {
  background:
    <image> <position> / <size>,
    <image> <position> / <size>,
    <image> <position> / <size>;
  background-repeat: no-repeat;
}

The structure above is the basis of how we’ll draw images—one line per shape. Keep in mind that the rendering order is the opposite of how absolutely- or fixed-position elements are ordered. The first one will show up on top instead of at the bottom. In other words, the circles (radial gradients) below would be rendered from bottom to top (blue on bottom, red on top).

.circles {
	background:
    radial-gradient(7em 7em at 35% 35%, red 50%, transparent 50%),
    radial-gradient(7em 7em at 50% 50%, gold 50%, transparent 50%),
    radial-gradient(7em 7em at 65% 65%, blue 50%, transparent 50%);
  background-repeat: no-repeat;
  width: 240px;
  height: 240px;
}

Drawing

We’ll use Sass (SCSS) to draw these images so we can make use of variables for a color palette. That will make the code shorter, easier to read and change darkened or lighter variants of colors. We could use variables in normal CSS instead and forget Sass, but due to Internet Explorer’s lack of support, let’s stick with Sass. To explain how this works, we’ll experiment with shapes using both linear and radial gradients.

Setting Up a Color Palette

Our palette will consist of RGB or HSL colors. I’ll explain later why to keep the colors in either of those formats. For this example, we’ll use RGB.

$r: rgb(255,0,0); // hsl(0,100%,50%)
$o: rgb(255,128,0); // hsl(32,100%,50%)

What I like to do to keep code short and easy to read is use a minimum of one letter to represent each color (e.g. $r for red). If using darker or lighter shades of one color, I add a d before the base letter or letters for dark or an l for light. I’d use $dr for dark red and $lr for light red. If there’s a need for more than two other shades, then I add a number at the end to indicate the shade level. For instance, $dr1 for dark red, $dr2 for a darker red, and $lr1 for light red, $lr2 for a lighter red. Such a palette would look like this (with dark first, normal next, and light last):

$dr1: rgb(224,0,0);
$dr2: rgb(192,0,0);
$r: rgb(255,0,0);
$lr1: rgb(255,48,48);
$lr2: rgb(255,92,92);

Setting the Scale and Canvas

We’ll use em units for the image dimensions so that the image can easily be resized proportionally. Since 1em is equal to the element’s font size, each unit of the image will be adjusted accordingly if changed. Let’s set a font size of 10px and set both the width and height to 24em. Units of 10px is the easiest to think in because if you mentally do the math, you instantly get 240 × 240px. Then just to see where the edges of the canvas are, we’ll use a 1px gray outline.

$r: rgb(255,0,0); // hsl(0,100%,50%)
$o: rgb(255,128,0); // hsl(32,100%,50%)

.image {
  background-repeat: no-repeat;
  font-size: 10px;
  outline: 1px solid #aaa;
  width: 24em;
  height: 24em;
}

Be mindful about using smaller font sizes, however; browsers have a minimum font size setting (for accessiblity reasons). If you set a font size of 4px and the minimum is 6px, it’ll be forced at 6px.

Furthermore, you can enable responsiveness just by using calc() and viewport units. Perhaps we can use something like calc(10px + 2vmin) if desired, but let’s stick with 10px for now.

Drawing Shapes

The fun part begins here. To draw a square that is 8 × 8em in the center, we use a linear-gradient() with two same-colored stops.

.image {
  background:
    linear-gradient($r, $r) 50% 50% / 8em 8em;
  ...
}

To mold it into something more like a trapezoid, set an angle of 60deg. At the same time, let’s add $T for transparent to our palette and then place both the stops for $r and $T at 63% (right before the bottom-right corner gets cut off).

$T: transparent;

.image {
  background:
    linear-gradient(60deg,$r 63%, $T 63%) 50% 50% / 8em 8em;
  ...
}

Setting both stops at the same value makes the slanted side as crisp as the others. If you look at it more closely though, it appears to be pixelated:

To correct this, we slightly adjust one of the stops (by 1% or roughly so) so that the edge is smooth enough. So, let’s change $r’s 63% to 62%.

This will be an issue with round edges as well while working with radial gradients, which we’ll see later. If you’re viewing this in a browser other than Safari, everything looks great even if transitioning to a non-transparent color instead (say orange). The problem with transitioning to transparent in Safari is that you’ll notice a bit of black lining at the slanted side.

This is because the transparent keyword in Safari is always black transparency, and we see some black as a result. I really wish Apple would fix this, but they never will. For the time being, let’s add a new $redT variable for red transparency under $r. Scrap the $T we used for transparent as we’ll no longer use it.

$rT: rgba(255,0,0,0); // hsla(0,100%,50%,0)

Then let’s replace transparent with $redT. This takes care of our Safari problem!

.image {
  background:
    linear-gradient(60deg,$r 62%, $rT 63%) 50% 50% / 8em 8em;
  ...
}

If you’ve been wondering why we weren’t using hex colors, Internet Explorer and Edge don’t support the #rgba and #rrggbbaa notations (yep, hex has had an alpha channel since late 2016 if you never knew), and we want this to work as cross-browser as possible. We also want to stay consistent with our choice of color format.

Now let’s move the shape vertically to 20% and draw an orange circle of the same dimensions. Also, add another variable for its transparent version. For the smooth edge, insert a 1% gap between the solid and transparent oranges.

$oT: rgba(255,128,0,0); // hsla(32,100%,50%,0)

.image {
  background:
    linear-gradient(60deg,$r 62%, $rT 63%) 50% 20% / 8em 8em,
    radial-gradient(8em 8em at 50% 80%, $o 49%, $oT 50%);
  ...
}

To maintain consistency with our sizing, the second color stop should be at 50% instead of 100%.

Positioning Shapes

The way gradients are positioned is based on whether the unit is fixed or a percentage. Suppose we turn both of the gradients into squares and try to place them all the way across the div horizontally.

.image {
  background:
    linear-gradient($r, $r) 24em 20% / 8em 8em,
    linear-gradient($o, $o) 100% 80% / 8em 8em;
  ...
}

The red square ends up completely off canvas (outlined), and the right side of the orange square touches the other side. Using fixed units is like placing absolutely positioned elements or drawing shapes in HTML5 canvas. It’s true in that sense that the point of origin is at the top left. When using percent and a set background size, the div gets “fake padding” of half the background size. At the same time, the background’s point of origin is centered (not to be confused with background-origin, which regards box corners).

Now, if we turned these gradients into radial gradients as circles and applied the same x-positions 24em and 100%, both end up at the other side cut in half. This is because the point of origin is always in the center if we write the background like so:

.image {
	background:
  	radial-gradient(8em 8em at 24em 20%, $r 49%, $rT 50%),
  	radial-gradient(8em 8em at 100% 80%, $o 49%, $oT 50%);
	...
}

If we rewrote the background so that the position and size are after the gradient and used 100% 100% at center, they’ll be positioned like the linear gradients. The red one ends up outside, and the orange one touches the right edge. The “fake padding” occurs once again with the orange.

.image {
  background:
    radial-gradient(100% 100% at center, $r 49%, $rT 50%) 24em 20% / 8em 8em,
    radial-gradient(100% 100% at center, $o 49%, $oT 50%) 100% 80% / 8em 8em;
  ...
}

There’s no single proper way to position shapes, but to place it like an absolutely or fixed positioned HTML element, use fixed units. If in need of a quick way to place a shape (using the position / size parameters) in the dead center, 50% is the best option as the shape’s origin will be its center. Use 100% if it should touch the very right side.

Sizing Shapes

Sizing in CSS backgrounds works as we’d expect, but it’s still influenced by the kind of unit used for position—fixed or percent. If we take our squares again and change their width to 10em, the red one expands to the right, and the orange one expands sideways.

.image {
  background:
    linear-gradient($r, $r) 12em 20% / 10em 8em,
    linear-gradient($o, $o) 50% 80% / 10em 8em;
  ...
}

If we used em units for the y-position, the shape will grow downwards or shrink upwards after changing height. If we use a percentage, then it will expand both vertical directions.

A moment ago, we looked at two ways to draw circles with radial gradients. The first way is to specify the width and height between the ( and at and then the position after that:

.image {
  background:
    radial-gradient(8em 8em at 50% 50%, $r 49%, $rT 50%);
  ...
}

The second way is to use 100% 100% in the center and then give the position and size:

.image {
  background:
    radial-gradient(100% 100% at 50% 50%, $r 49%, $rT 50%) 50% 50% / 8em 8em;
  ...
}

These methods both draw circles but will result in different outputs because:

  1. The first way occupies the whole div since there was no real background position or size.
  2. Giving a real position and size to the second sets it a bounding box. Consequently, it’ll behave just like a linear gradient shape.

Suppose we replaced $rT with $o. You’ll see that the orange will cover what was white or shapes under it (if we added any) for the first way. Using the second way, you’ll easily notice the bounding box revealed by the orange.

Additionally, the purpose of 100% 100% instead of using circle or ellipse is to allow the circle to occupy whole bounding box. It even gives us complete control over its dimensions. That way, it’ll remain the same if you change the 50% 50% position to something else. If using one of the two keywords, the edge of the circle stops only about 71% of the way when centered and becomes more distorted if its position is adjusted. For example, here’s what happens when we change the x-position to 0 for circle and ellipse:

In the long run, you can reimagine the syntax as radial-gradient(width height at x y) or radial-gradient(100% 100% at x-in-bounding-box y-in-bounding-box) x y / width height. If you are drawing just a circle or oval, you can simplify the code with the first way. If drawing part of a circle or part of a ring, then the second way comes into play. There will be many applications of that in the examples we’ll create next.

Examples

Ready to draw something for real now? We’ll walk through three examples step by step. The first two will be static—one with lots of half-circles and the other dealing with some rounded rectangles. The last example will be smaller but focused on animation.

Static Image

This parasol will be our first static image:

We’ll use a palette with red ($r and $rT), white ($w and $wT), orange ($o and $oT), and dark orange ($do and $doT).

$r: rgb(255,40,40);
$rT: rgba(255,40,40,0);

$w: rgb(240,240,240);
$wT: rgba(240,240,240,0);

$o: rgb(255,180,70);
$oT: rgba(255,180,70,0);

$do: rgb(232,144,0);
$doT: rgba(232,144,0,0);

Let’s set up our drawing area of 30 × 29em.

.parasol {
  // background to go here
  background-repeat: no-repeat;
  font-size: 10px;
  outline: 1px solid #aaa;
  width: 30em;
  height: 29em;
}

Above the background-repeat, we’ll begin drawing the parts of the parasol. First, add the gradients that make up the pole (since neither overlap one another, the bottom-to-top order doesn’t matter at this point):

.parasol {
   background:
     // 1
     radial-gradient(200% 200% at 100% 100%, $do 49%, $doT 50%) 14em 0 / 1em 1em,
     radial-gradient(200% 200% at 0% 100%, $o 49%, $oT 50%) 15em 0 / 1em 1em,
     // 2
     linear-gradient(90deg, $do 50%, $o 50%) 14em 1em / 2em 25em,
     // 3
     radial-gradient(100% 200% at 50% 0, $oT 0.95em, $o 1em, $o 1.95em, $do 2em, $do 2.95em, $doT 3em) 14em 26em / 6em 3em,
     // 4
     radial-gradient(200% 200% at 100% 100%, $o 49%, $oT 50%) 18em 25em / 1em 1em,
     radial-gradient(200% 200% at 0% 100%, $do 49%, $doT 50%) 19em 25em / 1em 1em;
   ...
}
  1. To draw each side of the top of the pole, we used quarters of a circle that are 1 × 1em. To make them occupy their bounding boxes, we used circles that are twice the size (200% 200%) positioned at the bottom right and at the bottom left. We could also use keyword pairs like right bottom or left bottom, but it’s shorter to use the percent equivalents. Notice the 1% gaps between the stops to ensure smoothness.
  2. For the long part, we used a long rectangle with an abrupt dark orange-to-orange. There’s no need for a fractional tiny gap since we’re not working with a curve or slant.
  3. This part of the pole is a bit trickier to draw than the others because we have to maintain the 2em diameter. To draw this arc, we use a box of 6 × 3em so that there is a 2em space between the ends that are also 2em. Then we use a radial gradient at the center top where each stop occurs 1em each (and spread by 0.05em for smoothness).
  4. The last two are just like the first except they are positioned at the right end of the arc so that they seamlessly fit. Also, the colors swap places.

Then above the previous gradients, add the following from bottom to top to draw the top of the umbrella without the pointy ends:

.parasol {
  background:
    radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 9em 12em,
    radial-gradient(100% 200% at 50% 100%, $w 50%, $wT 50.25%) 50% 1.5em / 21em 12em,
    radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 30em 12em,
  ...
}

To draw the half circles that make up this part, we used a gradient size of 100% 200%, which makes each diameter fit into its background width but have twice the height and centered at the bottom. By ordering them bottom to top so that the largest is on bottom and smallest on top, we get the curves we want.

As our stack of gradients grows taller, it’ll become difficult after a while to identify which background or group of backgrounds corresponds to what part of the image. So to make it easier to pin them down, we can split them into groups lead by a comment describing what each group is for. Right now, we have split the stack to groups for the top of the parasol and the pole.

.parasol {
  background:
    /* top */
    radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 9em 12em,
    radial-gradient(100% 200% at 50% 100%, $w 50%, $wT 50.25%) 50% 1.5em / 21em 12em,
    radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 30em 12em,
    
    /* pole */
    radial-gradient(200% 200% at 100% 100%, $do 49%, $doT 50%) 14em 0 / 1em 1em,
    radial-gradient(200% 200% at 0% 100%, $o 49%, $oT 50%) 15em 0 / 1em 1em,
    linear-gradient(90deg, $do 50%, $o 50%) 14em 1em / 2em 25em,
    radial-gradient(100% 200% at 50% 0, $oT 0.95em, $o 1em, $o 1.95em, $do 2em, $do 2.95em, $doT 3em) 14em 26em / 6em 3em,
    radial-gradient(200% 200% at 100% 100%, $o 49%, $oT 50%) 18em 25em / 1em 1em,
    radial-gradient(200% 200% at 0% 100%, $do 49%, $doT 50%) 19em 25em / 1em 1em;
    ...
}

Then, in between the top and the pole, we’ll add the next chunk of backgrounds to render the pointy ends. To determine the widths of each segment, we must get the distance between each point where red and white meet. They all must add up to 30em.

Starting with the white and narrowest red half circles, we subtract the red’s width of 9em from the white’s width of 21em and divide the result by 2 to get the width of the two white segments (point “b” in the figure). So, the result would be 6em ( b = (21 – 9) / 2 = 6 ). Then the middle red segment would be 9em (21 – (6 + 6) = 9). What we have left now are the outer red segments (point “a” in the figure). Subtract the sum of what we have now from the width of the larger red half circle and divide that result by 2. That would be make the value of point a: (30em – (6 + 6 + 9)) / 2 = 4.5em.

.parasol {
   background:
     ...
     /* pointy ends */
     radial-gradient() 0 13.5em / 4.5em 3em,
     radial-gradient() 4.5em 13.5em / 6em 3em,
     radial-gradient() 50% 13.5em / 9em 3em,
     radial-gradient() 19.5em 13.5em / 6em 3em,
     radial-gradient() 25.5em 13.5em / 4.5em 3em,
     ...
}

To draw the half circles similar to how we drew the top part, we start with the transparent counterpart of the color for each shape so that they resemble arc bridges. We’ll also add an extra 5% to each gradient width (not the background box width) so that each point formed by adjacent backgrounds won’t overly sharp and thin.

.parasol {
   background:
     ...
     /* pointy ends */
     radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 0 13.5em / 4.5em 3em,
     radial-gradient(105% 200% at 50% 100%, $wT 49%, $w 50%) 4.5em 13.5em / 6em 3em,
     radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 50% 13.5em / 9em 3em,
     radial-gradient(105% 200% at 50% 100%, $wT 49%, $w 50%) 19.5em 13.5em / 6em 3em,
     radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 25.5em 13.5em / 4.5em 3em,
     ...
}

Finally, you’ll no longer need that 1px solid #aaa outline. Our parasol is complete!

See the Pen Parasol by Jon Kantner (@jkantner) on CodePen.

Something With Rounded Rectangles

This next example will be an old iPhone model in which there are more details than the newer models. The thing about this one is the two rounded rectangles, which are the outside and middle of the home button.

The palette will consist of black ($bk and $bkT) for the home button edge, dark gray ($dg and$dgT) for the body, gray ($g and $gT) for the camera and speaker, light gray ($lg and $lgT) for the outside border, blue ($bl and $blT) for the camera lens, and a very dark purple ($p and $pT) for the screen.

$bk: rgb(10,10,10);
$bkT: rgba(10,10,10,0);

$dg: rgb(50,50,50);
$dgT: rgba(50,50,50,0);

$g: rgb(70,70,70);
$gT: rgba(70,70,70,0);

$lg: rgb(120,120,120);
$lgT: rgba(120,120,120,0);

$bl: rgb(20,20,120);
$blT: rgba(20,20,120,0);

$p: rgb(25,20,25);
$pT: rgba(25,20,25,0);

Let’s set up our canvas at 20 × 40em and use the same font size we used for the parasol, 10px:

.iphone {
  // background goes here
  background-repeat: no-repeat;
  font-size: 10px;
  outline: 1px solid #aaa;
  width: 20em;
  height: 40em;
}

Before we begin drawing our first rounded rectangle, we need to think about our border radius, which will be 2em. Also, we want to leave some space at the left for the lock switch and volume buttons, which will be 0.25em. For this reason, the rectangle will be 19.75 × 40em. Considering the 2em border radius, we’ll need two linear gradients intersecting each other. One must have a width of 15.75em (19.75em – 2 × 2), and the other must have a height of 36em (40em – 2 × 2). Position the first 2.25em from the left and then the second 0.25em from the left and 2em from the top.

.iphone {
  background:
    /* body */
    linear-gradient() 2.25em 0 / 15.75em 40em,
    linear-gradient() 0.25em 2em / 19.75em 36em;
  ...
}

Since the light gray border will be 0.5em thick, let’s make each gradient stop immediately switch from light gray ($lg) to dark gray ($dg) and vice versa at 0.5em and 0.5em before the end (40em – 0.5 = 39.5em for the first gradient, 19.75em – 0.5 = 19.25em for the second). Set an angle of 90deg for the second to make it go horizontal.

.iphone {
  background:
    /* body */
    linear-gradient($lg 0.5em, $dg 0.5em, $dg 39.5em, $lg 39.5em) 2.25em 0 / 15.75em 40em,
    linear-gradient(90deg, $lg 0.5em, $dg 0.5em, $dg 19.25em, $lg 19.25em) 0.25em 2em / 19.75em 36em;
  ...
}

In each square corner, as indicated by the orange box in the figure, we’ll place the rounded edges. To create those shapes, we use radial gradients that are twice the size of their bounding boxes and located in each corner. Insert them above the body backgrounds.

.iphone {
  background:
    /* corners */
    radial-gradient(200% 200% at 100% 100%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 0.25em 0 / 2em 2em,
    radial-gradient(200% 200% at 0% 100%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 18em 0 / 2em 2em,
    radial-gradient(200% 200% at 100% 0%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 0.25em 38em / 2em 2em,
    radial-gradient(200% 200% at 0% 0%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 18em 38em / 2em 2em,
    ...
}

To get the 0.5em-thick light gray ends, think about where the gradient starts and then do the math. Because the light gray is at the end, we subtract 0.5em from 2em to properly place first stop. For the smoothness, we take off a tiny bit from the first 1.5em and add 1% to the second 50% so that the round edges blend in with the flat edges.

Now if we enlarged the image by changing the font size to 40px or more, we notice seams between the rounded and flat edges (circled in orange):

Since they appear to be so tiny, we can easily patch them by going back to the body backgrounds and slightly altering the gradient stops as long as everything still looks right when changing the font size back to 10px.

.iphone {
  background:
    /* body */
    linear-gradient($lg 0.5em, $dg 0.55em, $dg 39.5em, $lg 39.55em) 2.25em 0 / 15.75em 40em,
    linear-gradient(90deg, $lg 0.5em, $dg 0.55em, $dg 19.175em, $lg 19.25em) 0.25em 2em / 19.75em 36em;
  ...
}

Then in one linear gradient, we’ll add the lock switch and volume buttons to fill the 0.25em space on the left. If a 1px space is going to happen between the buttons and body, we can add a tiny bleed of 0.05em to the background width (making it 0.3em) so that it won’t stick out into the dark gray.

.iphone {
  background:
    /* volume buttons */
    linear-gradient($lgT 5em, $lg 5em, $lg 7.5em, $lgT 7.5em, $lgT 9.5em, $lg 9.5em, $lg 11em, $lgT 11em, $lgT 13em, $lg 13em, $lg 14.5em, $lgT 14.5em) 0 0 / 0.3em 100%,
    ...
}

It looks like we could’ve used three light gray-to-light gray gradients, but since it was possible, only one was needed. It’s just lots of sudden transitions between the transparent and opaque light grays running downwards.

Next, let’s add the edge of the home button as well as the flat edges of the square inside it. Now the square inside home button will be 1.5 × 1.5em and follow basically the same procedure as the body: two intersecting linear gradients and radials to fill the corners. To place them horizontally in the center, calc() comes in handy. 50% + 0.125em will be the expression; if we centered only the phone body, there will be 0.125em spaces on each side. Therefore, we move it 0.125em more to the right. The same x-positioning will apply to the upper two backgrounds.

.iphone {
  background:
    /* home button */
    linear-gradient() calc(50% + 0.125em) 36.5em / 0.5em 1.5em,
    linear-gradient() calc(50% + 0.125em) 37em / 1.5em 0.5em,
    radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, $bkT 1.25em, $bk 1.3em, $bk 49%, $bkT 50%),
    ...
}

Similar to how we shaded the linear gradient parts of the phone body, the stops will begin and end with light gray but with transparent in the middle. Notice we left 0.05em gaps between each gray-to-transparent transition (and vice versa). Just like the corners of the body, this is to ensure the blend between a round corner and flat end inside.

.iphone {
  background:
    /* home button */
    linear-gradient($lg 0.15em, $lgT 0.2em, $lgT 1.35em, $lg 1.35em) calc(50% + 0.125em) 36.5em / 0.5em 1.5em,
    linear-gradient(90deg, $lg 0.15em, $lgT 0.2em, $lgT 1.3em, $lg 1.35em) calc(50% + 0.125em) 37em / 1.5em 0.5em,
    radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, $bkT 1.25em, $bk 1.3em, $bk 49%, $bkT 50%),
    ...
}

By the way, because the outlines will be so small as you’ve seen earlier, we can better see what we’re doing by increasing the font size to at least 20px. It’s like using the zoom tool in image editing software.

Now to get the corners of the gray square to exactly where they should be, let’s focus on the x-position first. We start with calc(50% + 0.125em), then we add or subtract the width of each piece, or should I say the square’s border radius. These backgrounds will go above the last three.

.iphone {
  background:
    /* home button */
    radial-gradient(200% 200% at 100% 100%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em - 0.5em) 36.5em / 0.5em 0.5em,
    radial-gradient(200% 200% at 0% 100%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em + 0.5em) 36.5em / 0.5em 0.5em,
    radial-gradient(200% 200% at 100% 0%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em - 0.5em) 37.5em / 0.5em 0.5em,
    radial-gradient(200% 200% at 0% 0%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em + 0.5em) 37.5em / 0.5em 0.5em,
    ...
}

Then the screen will be a 17.25 × 30em rectangle. Just like parts of the home button, we’ll horizontally center it using calc(50% + 0.125em). From the top, it’ll be 5em.

.iphone {
  background:
    /* screen */
    linear-gradient($p, $p) calc(50% + 0.125em) 5em / 17.25em 30em,
    ...
}

Lastly, we’ll add the camera and speaker. The camera is a straightforward 1 × 1 blue-to-gray radial with no fancy calculations. The pure-gray speaker though will be a bit more involved. It will be a 5 × 1em rectangle and have a 0.5em border radius. To draw that, we first draw a rectangle with the first 4ems of the width and center it with calc(50% + 0.125em). Then we use 0.5 × 1em half circles in which the gradient positions are 100% 50% and 0% 50%. The best way to position these left and right of the rectangle is to use some new calc() expressions. For the left, we’ll subtract half the rectangle width and half the half circle width from the body center (50% + 0.125em - 2em - 0.25em). The right will follow the same pattern but with addition, so 50% + 0.125em + 2em + 0.25em.

.iphone {
  background:
    /* camera */
    radial-gradient(1em 1em at 6.25em 2.5em, $bl 0.2em, $g 0.21em, $g 49%, $gT 50%),
    /* speaker */
    radial-gradient(200% 100% at 100% 50%, $g 49%, $gT 50%) calc(50% + 0.125em - 2em - 0.25em) 2em / 0.5em 1em,
    radial-gradient(200% 100% at 0% 50%, $g 49%, $gT 50%) calc(50% + 0.125em + 2em + 0.25em) 2em / 0.5em 1em,
    linear-gradient($g, $g) calc(50% + 0.125em) 2em / 4em 1em,
    ...
}

Remove the gray outline around the div, and the iPhone is complete!

See the Pen iPhone by Jon Kantner (@jkantner) on CodePen.

Animated Images

You might be thinking you could use background-position to animate these sorts of images, but you can only do so much. For instance, it’s impossible to animate the rotation of an individual background by itself. In fact, background-position animations don’t typically perform as well as transform animations, so I don’t recommend it.

To animate any part of an image any way we wish, we can let the :before or :after pseudo-elements be responsible for that part. If we need more selections, then we can revert to multiple child divs, yet not needing one for each little detail. For our animated image example, we’ll create this animated radar:

We draw the static part first, which is everything except the gray frame and rotating hand. Before that, let’s supply our palette (note: We won’t need a $dgnT for $dgn) and base code.

$gn: rgb(0,192,0);
$gnT: rgba(0,192,0,0);
$dgn: rgb(0,48,0);
$gy: rgb(128,128,128);
$gyT: rgba(128,128,128,0);
$bk: rgb(0,0,0);
$bkT: rgba(0,0,0,0);

.radar {
  background-repeat: no-repeat;
  font-size: 10px;
  outline: 1px solid #aaa;
  width: 20em;
  height: 20em;
}

Since this image is going to be completely round, we can safely apply a border radius of 50%. Then, we can use a repeating radial gradient to draw the rings—about 1/3 way apart from each other.

.radar {
  background:
    /* rings */
    repeating-radial-gradient($dgn, $dgn 2.96em, $gn 3em, $gn 3.26em, $dgn 3.3em);
  background-repeat: no-repeat;
  border-radius: 50%;
  ...
}

Also note the extra $dgn at the start. For repeating gradients to start, end, and loop as expected, we need to specify the starting color at 0 (or without 0).

Unlike the previous example, we’re not using calc() to center the lines because Internet Explorer will render the whole thing awkwardly when we use a pseudo-element later. To draw four 0.4em lines that intersect one other in the center, know that half of the line should be half the div at 10em. So then, we subtract and add half of 0.4 (0.4 / 2 = 0.2) on each side. In other words, the left of the green should be 9.8em, and the right should be 10.2em. For the 45deg diagonals though, we must multiply 10em by the square root of 2 to get their center (10 × √2 ≈ 14.14). It’s the length of the longest side of a 10em right triangle. As a result, the sides of each diagonal would be approximately at 13.94 and 14.34em.

.radar {
  background:
    /* lines */
    linear-gradient($gnT 9.8em, $gn 9.8em, $gn 10.2em, $gnT 10.2em),
    linear-gradient(45deg,$gnT 13.94em, $gn 13.98em, $gn 14.3em, $gnT 14.34em),
    linear-gradient(90deg,$gnT 9.8em, $gn 9.8em, $gn 10.2em, $gnT 10.2em),
    linear-gradient(-45deg,$gnT 13.94em, $gn 13.98em, $gn 14.3em, $gnT 14.34em),
  ...
}

To prevent the pixelation of the diagonals, we left a tiny 0.04em gap between green and transparent green. Then, to create some lighting, add this transparent-to-black radial gradient:

.radar {
  background:
    /* lighting */
    radial-gradient(100% 100%, $bkT, $bk 9.9em,$bkT 10em),
  ...
}

That completes the static part of the radar. Now we draw the gray frame and hand in another background stack under :before and add the animation. There’s a reason we didn’t include the frame here. Because the hand container should fit the whole div, we don’t want it to overlap the frame.

This pseudo-element shall fill the space, and to ensure it stays in there, let’s absolutely position it. We’ll use the same border radius so that it stays round while animated in Safari.

.radar {
  ...
  position: relative;
  &:before {
    background-repeat: no-repeat;
    border-radius: 50%;
    content: "";
    position: absolute;
    width: 100%;
    height: 100%;
  }
}

Then, to draw the hand, we make it half the size of its container and keep it at the top left corner. Finally, on top of that, we draw the frame.

.radar {
  ...
  &:before {
    animation: scan 5s linear infinite;
    background:
      /* frame */
      radial-gradient($gyT 9.20em, $gy 9.25em, $gy 10em, $gyT 10.05em),
      /* hand */
      linear-gradient(45deg, $gnT 6em, $gn) 0 0 / 50% 50%;
    ...
  }
}

@keyframes scan {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(1turn);
  }
}

Now our little gadget is complete!

See the Pen Radar by Jon Kantner (@jkantner) on CodePen.

Benefits (Plus a Drawback)

This approach of drawing CSS images has several advantages. First, the HTML will be very lightweight compared to a rasterized image file. Second, it’s great for tackling images that are impossible to draw well without using experimental properties and APIs that might not be widely supported.

It’s not to say that this method is better than using a parent element nested with children for the shapes. There is a drawback though. You have to give up being able to highlight individual shapes using the browser dev tools. You’ll need to comment and uncomment a background to identify which it is. As long as you group and label each chunk of backgrounds, you can find that particular background faster.

Conclusion

In a nutshell, the method for drawing of CSS images we’ve covered in this post allows us to:

  1. Set up a palette made up of variables for the colors.
  2. Disable the background repeat, set a scale with font-size, and a canvas width and height in em units for the target element.
  3. Use a temporary outline to show the edges as we work.
  4. Draw each shape from bottom to top because backgrounds are rendered in that order. The background syntax for each shape follows image position / size (with or without the position and size).

There’s a lot of thinking outside the box going on as well as experimentation to get the desired result. The three examples we created were just enough to demonstrate the concept. We’ve looked at how we order each background, drawing parts of circles, rounded rectangles, and slightly adjusting gradient stops for smooth edges. To learn more, feel free to dissect and study other examples I’ve made in this CodePen collection!


Drawing Images with CSS Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/drawing-images-with-css-gradients/feed/ 8 272923