Of Course We Can Make a CSS-Only Clock That Tells the Current Time!

Let’s build a fully functioning and settable “analog” clock with CSS custom properties and the calc() function. Then we’ll convert it into a “digital” clock as well. All this with no JavaScript!

Here’s a quick look at the clocks we’ll make:

Brushing up on the calc() function

CSS preprocessors teased us forever with the ability to calculate numerical CSS values. The problem with pre-processors is that they lack knowledge of the context after the CSS code has compiled. This is why it’s impossible to say you want your element width to be 100% of the container minus 50 pixels. This produces an error in a preprocessor:

width: 100% - 50px;
// error: Incompatible units: 'px' and '%'

Preprocessors, as their name suggests, preprocess your instructions, but their output is still just plain old CSS which is why they can’t reconcile different units in your arithmetic operations. Ana has gone into great detail on the conflicts between Sass and CSS features.

The good news is that native CSS calculations are not only possible, but we can even combine different units, like pixels and percentages with the calc() function:

width: calc(100% - 50px);

calc() can be used anywhere a length, frequency, angle, time, percentage, number, or integer is allowed. Check out the CSS Guide to CSS Functions for a complete overview.

What makes this even more powerful is the fact that you can combine calc() with CSS custom properties—something preprocessors are unable to handle.

The hands of the clock

Image of a round clock with a light grey background. The hours hand is dark blue pointed at 2, the minutes are light blue pointed at 9 and the seconds are yellow and pointed at 2.

Let’s lay out the foundations first with a few custom properties and the animation definition for the hands of the analog clock:

:root {
  --second: 1s;
  --minute: calc(var(--second) * 60);
  --hour: calc(var(--minute) * 60);
}
@keyframes rotate {
  from { transform: rotate(0); }
  to { transform: rotate(1turn); }
}

Everything starts in the root context with the --second custom property where we defined that a second should be, well, one second (1s). All future values and timings will be derived from this.

This property is essentially the heart of our clock and controls how fast or slow all of the clock’s hands go. Setting --second to 1s makes the clock match real-life time but we could make it go at half speed by setting it to 2s, or even 100 times faster by setting it to 10ms.

The first property we are calculating is the --minute hand, which we want equal to 60 times one second. We can reference the value from the --second property and multiply it by 60 with the help of calc() :

--minute: calc(var(--second) * 60);

The --hour hand property is defined using the exact same principle but multiplied by the --minute hand value:

--hour: calc(var(--minute) * 60);

We want all three hands on the clock to rotate from 0 to 360 degrees—around the shape of the clock face! The difference between the three animations is how long it takes each to go all the way around. Instead of using 360deg as our full-circle value, we can use the perfectly valid CSS value of 1turn.

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

These @keyframes simply tell the browser to turn the element around once during the animation. We have defined an animation named rotate and it is now ready to be assigned to the clock’s second hand:

.second.hand {
  animation: rotate steps(60) var(--minute) infinite;
}

We’re using the animation shorthand property to define the details of the animation. We added the name of the animation (rotate), how long we want the animation to run (var(--minute), or 60 seconds) and how many times to run it (infinite, meaning it never stops running). steps(60) is the animation timing function which tells the browser to perform the 1-turn animation in 60 equal steps. This way, the seconds hand ticks at each second rather than rotating smoothly along the circle.

While we are discussing CSS animations, we can define an animation delay (animation-delay) if we want the animation to start later, and we can change whether the animation should play forwards or backwards using animation-direction. We can even pause and restart the animations with animation-play-state.

The animation on the minute and hour hands will work very much like on the second hand. The difference is that multiple steps are unnecessary here—these hands can rotate in a smooth, linear fashion.

The minute hand takes one hour to complete one full turn, so:

.minute.hand {
  animation: rotate linear var(--hour) infinite;
}

On the other hand (pun intended) the hour hand takes twelve hours to go around the clock. We don’t have a separate custom property for this amount of time, like --half-day, so we will multiply --hour by twelve:

.hour.hand {
  animation: rotate linear calc(var(--hour) * 12) infinite;
}

You probably get the idea of how the hands of the clock work by now. But it would not be a complete example if we didn’t actually build the clock.

The clock face

So far, we’ve only looked at the CSS aspect of the clock. We also need some HTML for all that to work. Here’s what I’m using:

<main>
  <div class="clock">
    <div class="second hand"></div>
    <div class="minute hand"></div>
    <div class="hour hand"></div>
  </div>
</main>

Let’s see what we have to do to style our clock:

.clock {
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background-color: var(--grey);
  margin: 0 auto;
  position: relative;
}

We made the clock 300px tall and wide, made the background color grey (using a custom property, --grey, we can define later) and turned it into a circle with a 50% border radius.

There are three hands on the clock. Let’s first move these to the center of the clock face with absolute positioning:

.hand {
  position: absolute;
  left: 50%;
  top: 50%;
}

Notice the name of this class (.hands) because all three hands use it for their base styles. That’s important because any changes to this class are applied to all three hands.

Let’s define the hand dimensions and color things up:

.hand {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10px;
  height: 150px;
  background-color: var(--blue);
}

The hands are now all in place:

A round clock with just one light blue hand pointing directly at 6.

Getting proper rotation

Let’s hold off celebrating just a moment. We have a few issues and they might not be obvious when the clock hands are this thin. What if we change them to be 100px wide:

A round clock with a light grey background. The light blue hand is thick and still pointed at 6, but

We can now see that if an element is positioned 50% from the left, it is aligned to the center of the parent element—but that’s not exactly what we need. To fix this, the left coordinate needs to be 50%, minus half the width of the hand, which is 50px in our case:

Grey circle with a red vertical line bisecting it in the center. Two red arrows are on the clock, one labeled 50% pointing at the red line and another labeled negative 50 pixels pointing away from the red line.

Working with multiple different measurements is a breeze for the calc() function:

.hand {
  position: absolute;
  left: calc(50% - 50px);
  top: 50%;
  width: 100px;
  height: 150px;
  background-color: var(--grey);
}

This fixes our initial positioning, however, if we try to rotate the element we can see that the transform origin, which is the pivot point of the rotation, is at the center of the element:

Grey circle showing a blue rectangle on top rotated counter-clockwise.

We can use the transform-origin property to change the rotation origin point to be at the center of the x-axis and at the top on the y-axis:

.hand {
  position: absolute;
  left: calc(50% - 50px);
  top: 50%;
  width: 100px;
  height: 150px;
  background-color: var(--grey);
  transform-origin: center 0;
}

This is great, but not perfect because our pixel values for the clock hands are hardcoded. What if we want our hands to have different widths and heights, and scale with the actual size of the clock? Yes, you guessed right: we need a few CSS custom properties!

.second {
  --width: 5px;
  --height: 140px;
  --color: var(--yellow);
}
.minute {
  --width: 10px;
  --height: 90px;
  --color: var(--blue);
}
.hour {
  --width: 10px;
  --height: 50px;
  --color: var(--dark-blue);
}

With this, we’ve defined custom properties for the individual hands. What’s interesting here is that we gave these properties the same names: --width, --height, and --color. How is it possible that we gave them different values but they don’t overwrite each other? And which value will I get back if I call var(--width), var(--height) or var(--color)?

Let’s look at the hour hand:

<div class="hour hand"></div>

We assigned new custom properties to the .hour class and they are locally scoped to the element, which includes the element and all its children. This means any CSS style applied to the element—or its children accessing the custom properties—will see and use the specific values that were set within their own scope. So if you call var(--width) inside the hour hand element or any ancestor elements inside that, the value returned from our example above is 10px. This also means that if we try using any of these properties outside these elements—for example inside the body element—they are inaccessible to those elements outside the scope.

Moving on to the hands for seconds and minutes, we enter a different scope. That means the custom properties can be redefined with different values.

.second {
  --width: 5px;
  --height: 140px;
  --color: var(--yellow);
}
.minute {
  --width: 10px;
  --height: 90px;
  --color: var(--blue);
}
.hour {
  --width: 10px;
  --height: 50px;
  --color: var(--dark-blue);
}
.hand {
  position: absolute;
  top: 50%;
  left: calc(50% - var(--width) / 2);
  width: var(--width);
  height: var(--height);
  background-color: var(--color);
  transform-origin: center 0;
}

The great thing about this is that the .hand class (which we assigned to all three hand elements) can reference these properties for the calculations and declarations. And each hand will receive its own properties from its own scope. In a way, we’re personalizing the .hand class for each element to avoid unnecessary repetition in our code.

Our clock is up and running and all hands are moving at the correct speed:

The finished clock. Grey background, with minutes and hours pointing to 6 and seconds pointing to 7.

We could stop here but let me suggest a few improvements. The hands on the clock start at 6 o’clock, but we could set their initial positions to 12 o’clock by rotating the clock 180 degrees. Let’s add this line to the .clock class:

.clock {
  /* same as before */
  transform: rotate(180deg);
}

The hands might look nice with rounded edges:

.hand {
  /* same as before */
  border-radius: calc(var(--width) / 2);
}

Our clock looks and works great! And all hands start from 12 o’clock, exactly how we want it!

Setting the clock

Even with all these awesome features, the clock is unusable as it fails terribly at telling the actual time. However, there are some hard limitations to what we can do about this. It’s simply not possible to access the local time with HTML and CSS to automatically set our clock. But we can prepare it for manual setup.

We can set the clock to start at a certain hour and minute and if we run the HTML at exactly that time it will keep the time accurately afterwards. This is basically how you set a real-world clock, so I think this is an acceptable solution. 😅

Let’s add the time we want to start the clock as custom properties inside the .clock class:

.clock {
  --setTimeHour: 16;
  --setTimeMinute: 20;
  /* same as before */
}

The current time for me as I write is coming up to 16:20 (or 4:20) so the clock will be set to that time. All I need to do is refresh the page at 16:20 and it will keep the time accurately.

OK, but… how can we set the time to these positions and rotate the hands if a CSS animation is already controlling the rotation?

Ideally, we want to rotate and set the hands of the clock to a specific position when the animation starts at the very beginning. Say you want to set the hour hand to 90 degrees so it starts at 3:00 pm and initialize the rotation animation from this position:

/* this will not work */
.hour.hand {
  transform: rotate(90deg);
  animation: rotate linear var(--hour) infinite;
}

Well, unfortunately, this will not work because the transform is immediately overridden by the animation, as it modifies the very same transform property. So, no matter what we set this to, it will go back to 0 degrees where the first keyframe of the animation starts.

We can set the rotation of the hand independently from the animation. For example, we could wrap the hand into an extra element. This extra parent element, the “setter,” would be responsible for setting the initial position, then the hand element inside could animate from 0 to 360 degrees independently. The starting 0-degree position would then be relative to what we set the parent setter element to.

This would work but luckily there’s a better option! Let me amaze you! 🪄✨✨

The animation-delay property is what we usually use to start the animation with some predefined delay.

The trick is to use a negative value, which starts the animation immediately, but from a specific point in the animation timeline!

To start 10 seconds into the animation:

animation-delay: -10s;

In case of the hour and minute hands, the actual value we need for the delay is calculated from the --setTimeHour and --setTimeMinute properties. To help with the calculation, let’s create two new properties with the amount of time shifting we need:

  • The hour hand needs to be the hour we want to set the clock, multiplied by an hour.
  • The minute hand shifts the minute we want to set the clock multiplied by a minute.
--setTimeHour: 16;
--setTimeMinute: 20;
--timeShiftHour: calc(var(--setTimeHour) * var(--hour));
--timeShiftMinute: calc(var(--setTimeMinute) * var(--minute));

These new properties contain the exact amount of time we need for the animation-delay property to set our clock. Let’s add these to our animation definitions:

.second.hand {
  animation: rotate steps(60) var(--minute) infinite;
}
.minute.hand {
  animation: rotate linear var(--hour) infinite;
  animation-delay: calc(var(--timeShiftMinute) * -1);
}
.hour.hand {
  animation: rotate linear calc(var(--hour) * 12) infinite;
  animation-delay: calc(var(--timeShiftHour) * -1);
}

Notice how we multiplied these values by -1 to convert them to a negative number.

We have almost reached perfection with this, but there’s a slight issue: if we set the number of minutes to 30, for example, the hour hand needs to already be halfway through to the next hour. An even worse situation would be to set the minutes to 59 and the hour hand is still at the beginning of the hour.

fix this, all we need to do is add the minute shift and the hour shift values together for the hour hand:

.hour.hand {
  animation: rotate linear calc(var(--hour) * 12) infinite;
  animation-delay: calc(
    (var(--timeShiftHour) + var(--timeShiftMinute)) * -1
  );
}

And I think with this we have fixed everything. Let’s admire our beautiful, pure CSS, settable, analog clock:

Let’s go digital

In principle, an analog and a digital clock both use the same calculations, the difference being the visual representation of the calculations.

Clock with a rectangular light grey background with the numeric time reading 14 45 18 in 24-hour format.

Here’s my idea: we can create a digital clock by setting up tall, vertical columns of numbers and animate these instead of rotating the clock hands. Removing the overflow mask from the final version of the clock container reveals the trick:

The new HTML markup needs to contain all the numbers for all three sections of the clock from 00 to 59 on the second and minute sections and 00 to 23 on the hour section:

<main>
  <div class="clock">
    <div class="hour section">
      <ul>
        <li>00</li>
        <li>01</li>
        <!-- etc. -->
        <li>22</li>
        <li>23</li>
      </ul>
    </div>
    <div class="minute section">
      <ul>
        <li>00</li>
        <li>01</li>
        <!-- etc. -->
        <li>58</li>
        <li>59</li>
      </ul>
    </div>
    <div class="second section">
      <ul>
        <li>00</li>
        <li>01</li>
        <!-- etc. -->
        <li>58</li>
        <li>59</li>
      </ul>
    </div>
  </div>
 </main>

To make these numbers line up, we need to write some CSS, but to get started with the styling we can copy over the custom properties from the :root scope of the analog clock straight away, as time is universal:

:root {
  --second: 1s;
  --minute: calc(var(--second) * 60);
  --hour: calc(var(--minute) * 60);
}

The outermost wrapper element, the .clock, still has the very same custom properties for setting the initial time. All that’s changed is that it becomes a flexbox container:

.clock {
  --setTimeHour: 14;
  --setTimeMinute: 01;
  --timeShiftHour: calc(var(--setTimeHour) * var(--hour));
  --timeShiftMinute: calc(var(--setTimeMinute) * var(--minute));

  width: 150px;
  height: 50px;
  background-color: var(--grey);
  margin: 0 auto;
  position: relative;
  display: flex;
}

The three unordered lists and the list items inside them that hold the numbers don’t need any special treatment. The numbers will stack on top of each other if their horizontal space is limited. Let’s just make sure that there is no list styling to prevent bullet points and that we center things for consistent placement:

.section > ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.section > ul > li {
  width: 50px;
  height: 50px;
  font-size: 32px;
  text-align: center;
  padding-top: 2px;
}

The layout is done!

Outside each unordered list is a <div> with a .section class. This is the element that wraps each section, so we can use it as a mask to hide the numbers that fall outside the visible area of the clock:

.section {
  position: relative;
  width: calc(100% / 3);
  overflow: hidden;
}

The structure of the clock is done and the rails are now ready to be animated.

Animating the digital clock

The basic idea behind the whole animation is that the three strips of numbers can be moved up and down within their masks to show different numbers from 0 to 59 for seconds and minutes, and 0 to 23 for hours (for a 24-hour format).

We can do this by changing the translateY transition function in CSS for the individual strips of numbers from 0 to -100%. This is because 100% on the y-axis represents the height of the whole strip. A value of 0% will show the first number, and 100% will show the last number of the current strip.

Previously, our animation was based on rotating a hand from 0 to 360 degrees. We now have a different animation that moves the number strips from 0 to -100% on the y-axis:

@keyframes tick {
  from { transform: translateY(0); }
  to { transform: translateY(-100%); }
}

Applying this animation to the seconds number strip can be done the same way as the analog clock. The only difference is the selector and the name of the animation that’s referenced:

.second > ul {
  animation: tick steps(60) var(--minute) infinite;
}

With the step(60) setting, the animation ticks between numbers like we did for the second hand on the analog clock. We could change this to ease and then the numbers would smoothly slide up and down as if they were on a ribbon of paper.

Assigning the new tick animation to the minute and hour sections is just as straightforward:

.minute > ul {
  animation: tick steps(60) var(--hour) infinite;
  animation-delay: calc(var(--timeShiftMinute) * -1);
}
.hour > ul {
  animation: tick steps(24) calc(24 * var(--hour)) infinite;
  animation-delay: calc(var(--timeShiftHour) * -1);
}

Again, the declarations are very similar, what’s different this time is the selector, the timing function, and the animation name.

The clock now ticks and keeps the correct time:

Time reading 14 1 10 in 24-hour format.

One more detail: The blinking colon (:)

Again we could stop here and call it a day. But there’s one last thing we can do to make our digital clock a little more realistic: make the colon separator between the minutes and seconds blink as each second passes.

We could add these colons in the HTML but they are not part of the content. We want them to be an enhancement to the appearance and style of the clock, so CSS is the right place to store this content. That’s what the content property is for and we can use it on the ::after pseudo-elements for the minutes and hours:

.minute::after,
.hour::after {
  content: ":";
  margin-left: 2px;
  position: absolute;
  top: 6px;
  left: 44px;
  font-size: 24px;
}

That was easy! But how can we make the seconds colon blink too? We want it animated so we need to define a new animation? There are many ways to achieve this but I thought we should change the content property this time to demonstrate that, quite unexpectedly, it is possible to change its value during an animation:

@keyframes blink {
  from { content: ":"; }
  to { content: ""; }
}

Animating the content property is not going to work in every browser, so you could just change that to opacity or visibility as a safe option…

The final step is to assign the blink animation to the pseudo-element of the minute section:

.minute::after {
  animation: blink var(--second) infinite;
}

And with that, we are all done! The digital clock keeps the time accurately and we even managed to add a blinking separator between the numbers.

Book: All you need is HTML and CSS

This is just one example project from my new book, All you need is HTML and CSS. It’s available on Amazon in both the U.S and U.K.

If you are just getting started with web development, the ideas in the book will help you level up and build interactive, animated web interfaces without touching any JavaScript.

If you are a seasoned JavaScript developer, the book is a good reminder that many things can be built with HTML and CSS alone, especially now that we have a lot more powerful CSS tools and features, like the ones we covered in this article. There are many examples in the book pushing the limits including interactive carousels, accordions, calculating, counting, advanced input validation, state management, dismissible modal windows, and reacting to mouse and keyboard inputs. There’s even a fully working star rating widget and a shopping basket.

Thanks for spending the time to build clocks with me!


The post Of Course We Can Make a CSS-Only Clock That Tells the Current Time! appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

The Raven Technique: One Step Closer to Container Queries

For the millionth time: We need container queries in CSS! And guess what, it looks like we’re heading in that direction.

When building components for a website, you don’t always know how that component will be used. Maybe it will be render as wide as the browser window is. Maybe two of them will sit side by side. Maybe it will be in some narrow column. The width of it doesn’t always correlate with the width of the browser window.

It’s common to reach a point where having container based queries for the CSS of the component would be super handy. If you search around the web for solution to this, you’ll probably find several JavaScript-based solutions. But those come at a price: extra dependencies, styling that requires JavaScript, and polluted application logic and design logic.

I am a strong believer in separation of concerns, and layout is a CSS concern. For example, as nice of an API as IntersectionObserver is, I want things like :in-viewport in CSS! So I continued searching for a CSS-only solution and I came across Heydon Pickering’s The Flexbox Holy Albatross. It is a nice solution for columns, but I wanted more. There are some refinements of the original albatross (like The Unholy Albatross), but still, they are a little hacky and all that is happening is a rows-to-columns switch.

I still want more! I want to get closer to actual container queries! So, what does CSS have offer that I could tap into? I have a mathematical background, so functions like calc(), min(), max() and clamp() are things I like and understand.

Next step: build a container-query-like solution with them.

Want to see what is possible before reading on? Here is a CodePen collection showing off what can be done with the ideas discussed in this article.


Why “Raven”?

This work is inspired by Heydon’s albatross, but the technique can do more tricks, so I picked a raven, since ravens are very clever birds.

Recap: Math functions in CSS

The calc() function allows mathematical operations in CSS. As a bonus, one can combine units, so things like calc(100vw - 300px) are possible.

The min() and max() functions take two or more arguments and return the smallest or biggest argument (respectively).

The clamp() function is like a combination of min() and max() in a very useful way. The function clamp(a, x, b) will return:

  • a if x is smaller than a
  • b if x is bigger than b and
  • x if x is in between a and b

So it’s a bit like clamp(smallest, relative, largest). One may think of it as a shorthand for min(max(a,x),b). Here’s more info on all that if you’d like to read more.

We’re also going to use another CSS tool pretty heavily in this article: CSS custom properties. Those are the things like --color: red; or --distance: 20px. Variables, essentially. We’ll be using them to keep the CSS cleaner, like not repeating ourselves too much.

Let’s get started with this Raven Technique.

Step 1: Create configuration variables

Let’s create some CSS custom properties to set things up.

What is the base size we want our queries to be based on? Since we’re shooting for container query behavior, this would be 100% — using 100vw would make this behave like a media query, because that’s the width of the browser window, not the container!

--base_size: 100%;

Now we think about the breakpoints. Literally container widths where we want a break in order to apply new styles.

--breakpoint_wide: 1500px; 
/* Wider than 1500px will be considered wide */
--breakpoint_medium: 800px;
/* From 801px to 1500px will be considered medium */
/* Smaller than or exact 800px will be small */

In the running example, we will use three intervals, but there is no limit with this technique.

Now let’s define some (CSS length) values we would like to be returned for the intervals defined by the breakpoints. These are literal values:

--length_4_small: calc((100% / 1) - 10px); /* Change to your needs */
--length_4_medium: calc((100% / 2) - 10px); /* Change to your needs */
--length_4_wide: calc((100% / 3) - 10px); /* Change to your needs */

This is the config. Let’s use it!

Step 2: Create indicator variables

We will create some indicator variables for the intervals. They act a bit like boolean values, but with a length unit (0px and 1px). If we clamp those lengths as minimum and maximum values, then they serve as a sort of “true” and “false” indicator.

So, if, and only if --base_size is bigger than --breakpoint_wide, we want a variable that’s 1px. Otherwise, we want 0px. This can be done with clamp():

--is_wide: clamp(0px,
  var(--base_size) - var(--breakpoint_wide),
  1px
);

If var(--base_size) - var(--breakpoint_wide) is negative, then --base_size is smaller than --breakpoint_wide, so clamp() will return 0px in this case.

Conversely, if --base_size is bigger than --breakpoint_wide, the calculation will give a positive length, which is bigger than or equal to 1px. That means clamp() will return 1px.

Bingo! We got an indicator variable for “wide.”

Let’s do this for the “medium” interval:

--is_medium: clamp(0px,
  var(--base_size) - var(--breakpoint_medium),
  1px
); /*  DO NOT USE, SEE BELOW! */

This will give us 0px for the small interval, but 1px for the medium and the wide interval. What we want, however, is 0px for the wide interval and 1px for the medium interval exclusively.

We can solve this by subtracting --is_wide value. In the wide interval, 1px - 1px is 0px; in the medium interval 1px - 0px is 1px; and for the small interval 0px - 0px gives 0px. Perfect.

So we get:

--is_medium: calc(
  clamp(0px, 
  var(--base_size) - var(--breakpoint_medium), 
  1px) 
  - var(--is_wide)
); 

See the idea? To calculate an indicator variable, use clamp() with 0px and 1px as borders and the difference of --base_width and --breakpoint_whatever as the clamped value. Then subtract the sum of all indicators for bigger intervals. This logic produces the following for the smallest interval indicator:

--is_small: calc(
  clamp(0px,
    (var(--base_size) - 0px,
    1px)
  - (var(--is_medium) + var(--is_wide))
); 

We can skip the clamp here because the breakpoint for small is 0px and --base_size is positive, so --base_size - 0px is alway bigger than 1px and clamp() will always return 1px. Therefore, the calculation of --is_small can be simplified to:

--is_small: calc(1px - (var(--is_medium) + var(--is_wide))); 

Step 3: Use indicator variables to select interval values

Now we need to go from these “indicator variables” to something useful. Let’s assume we’re working with a pixel-based layout. Don’t panic, we will handle other units later.

Here’s a question. What does this return?

calc(var(--is_small) * 100);

If --is_small is 1px, it will return 100px and if --is_small is 0px, it will return 0px.

How is this useful? See this:

calc(
  (var(--is_small) * 100) 
  +
  (var(--is_medium) * 200) 
);

This will return 100px + 0px = 100px in the small interval (where --is_small is 1px and --is_medium is 0px). In the medium interval (where --is_medium is 1px and --is_small is 0px), it will return 0px + 200px = 200px.

Do you get the idea? See Roman Komarov’s article for a deeper look at what is going on here because it can be complex to grasp.

You multiply a pixel value (without a unit) by the corresponding indicator variable and sum up all these terms. So, for a pixel based layout, something like this is sufficient:

width: calc(
    (var(--is_small)  * 100) 
  + (var(--is_medium) * 200) 
  + (var(--is_wide)   * 500) 
  );

But most of the time, we don’t want pixel-based values. We want concepts, like “full width” or “third width” or maybe even other units, like 2rem, 65ch, and the like. We’ll have to keep going here for those.

Step 4: Use min() and an absurdly large integer to select arbitrary-length values

In the first step, we defined something like this instead of a static pixel value:

--length_4_medium: calc((100% / 2) - 10px);

How can we use them then? The min() function to the rescue!

Let’s define one helper variable:

--very_big_int: 9999; 
/* Pure, unitless number. Must be bigger than any length appearing elsewhere. */

Multiplying this value by an indicator variable gives either 0px or 9999px. How large this value should be depends on your browser. Chrome will take 999999, but Firefox will not accept that high of a number, so 9999 is a value that will work in both. There are very few viewports larger than 9999px around, so we should be OK.

What happens, then, when we min() this with any value smaller than 9999px but bigger than 0px?

min(
  var(--length_4_small), 
  var(--is_small) * var(--very_big_int) 
);

If, and only if --is_small is 0px, it will return 0px. If --is_small is 1px, the multiplication will return 9999px (which is bigger than --length_4_small), and min will return: --length_4_small.

This is how we can select any length (that is, smaller than 9999px but bigger than 0px) based on indicator variables.

If you deal with viewports larger than 9999px, then you’ll need to adjust the --very_big_int variable. This is a bit ugly, but we can fix this the moment pure CSS can drop the unit on a value in order to get rid of the units at our indicator variables (and directly multiply it with any length). For now, this works.

We will now combine all the parts and make the Raven fly!

Step 5: Bringing it all together

We can now calculate our dynamic container-width-based, breakpoint-driven value like this:

--dyn_length: calc(
    min(var(--is_wide)   * var(--very_big_int), var(--length_4_wide)) 
  + min(var(--is_medium) * var(--very_big_int), var(--length_4_medium))
  + min(var(--is_small)  * var(--very_big_int), var(--length_4_small))
);

Each line is a min() from Step 4. All lines are added up like in Step 3, the indicator variables are from Step 2 and all is based on the configuration we did in Step 1 — they work all together in one big formula!

Want to try it out? Here is a is a Pen to play with (see the notes in the CSS).

This Pen uses no flexbox, no grid, no floats. Just some divs. This is to show that helpers are unnecessary in this kind of layout. But feel free to use the Raven with these layouts too as it will help you do more complex layouts.

Anything else?

So far, we’ve used fixed pixel values as our breakpoints, but maybe we want to change layout if the container is bigger or smaller than half of the viewport, minus 10px? No problem:

--breakpoint_wide: calc(50vw - 10px);

That just works! Other formulas work as well. To avoid strange behavior, we want to use something like:

--breakpoint_medium: min(var(--breakpoint_wide), 500px);

…to set a second breakpoint at 500px width. The calculations in Step 2 depend on the fact that --breakpoint_wide is not smaller than --breakpoint_medium. Just keep your breakpoints in the right order: min() and/or max() are very useful here!

What about heights?

The evaluations of all the calculations are done lazily. That is, when assigning --dyn_length to any property, the calculation will be based on whatever --base_size evaluates to in this place. So setting a height will base the breakpoints on 100% height, if --base_size is 100%.

I have not (yet) found a way to set a height based on the width of a container. So, you can use padding-top since 100% evaluates to the width for padding.

What about showing and hiding things?

The simplest way to show and hide things the Raven way is to set the width to 100px (or any other suitable width) at the appropriate indicator variable:

.show_if_small {
  width: calc(var(--is_small) * 100);
}
.show_if_medium {
  width: calc(var(--is_medium) * 100);
}
.show_if_wide {
  width: calc(var(--is_wide) * 100);
}

You need to set:

overflow: hidden;
display: inline-block; /* to avoid ugly empty lines */

…or some other way to hide things within a box of width: 0px. Completely hiding the box requires setting additional box model properties, including margin, padding and border-width, to 0px . The Raven can do this for some properties, but it’s just as effective to fix them to 0px.

Another alternative is to use position: absolute; and draw the element off-screen via left: calc(var(--is_???) * 9999);.

Takeaways

We might not need JavaScript at all, even for container query behavior! Certainly, we’d hope that if we actually get container queries in the CSS syntax, it will be a lot easier to use and understand — but it’s also very cool that things are possible in CSS today.

While working on this, I developed some opinions about other things CSS could use:

  • Container based units like cw and ch to set heights based on width. These units could be based on the root element of the current stacking context.
  • Some sort of “evaluate to value” function, to overcome problems with lazy evaluation. This would work great with a “strip unit” function that works at render time.

If we had that second one, it would allow us to set colors (in a clean way), borders, box-shadow, flex-grow, background-position, z-index, scale(), and other things with the Raven.

Together with component-based units, setting child dimensions to the same aspect-ratio as the parent would even be possible. Dividing by a value with unit is not possible; otherwise --indicator / 1px would work as “strip unit” for the Raven.

Bonus: Boolean logic

Indicator variables look like boolean values, right? The only difference is they have a “px” unit. What about the logical combination of those? Imagine things like “container is wider than half the screen” and “layout is in two-column mode.” CSS functions to the rescue again!

For the OR operator, we can max() over all of the indicators:

--a_OR_b: max( var(--indicator_a) , var(--indicator_b) );

For the NOT operator, we can subtract the indicator from 1px:

--NOT_a: calc(1px - var(--indicator_a));

Logic purists may stop here, since NOR(a,b) = NOT(OR(a,b)) is complete boolean algebra. But, hey, just for fun, here are some more:

AND:

--a_AND_b: min(var(--indicator_a), var(--indicator_b)); 

This evaluates to 1px if and only if both indicators are 1px.

Note that min() and max() take more than two arguments. They still work as an AND and OR for (more than two) indicator variables.

XOR:

--a_XOR_b: max(
  var(--indicator_a) - var(--indicator_b), 
  var(--indicator_b) - var(--indicator_a)
);

If (and only if) both indicators have the same value, both differences are 0px, and max() will return this. If the indicators have different values, one term will give -1px, the other will give 1px. max() returns 1px in this case.

If anyone is interested in the case where two indicators are equal, use this:

--a_EQ_b: calc(1px - 
  max(
    var(--indicator_a) - var(--indicator_b), 
    var(--indicator_b) - var(--indicator_a)
  )
);

And yes, this is NOT(a XOR b). I was unable to find a “nicer” solution to this.

Equality may be interesting for CSS length variables in general, rather than just being used for indicator variables. By using clamp() once again, this might help:

--a_EQUALS_b_general: calc(
  1px -
  clamp(0px,
        max(
          var(--var_a) - var(--var_b),
          var(--var_b) - var(--var_a)
        ),
        1px)
  );

Remove the px units to get general equality for unit-less variables (integers).

I think this is enough boolean logic for most layouts!

Bonus 2: Set the number of columns in a grid layout

Since the Raven is limited to return CSS length values, it is unable to directly choose the number of columns for a grid (since this is a value without a unit). But there is a way to make it work (assuming we declared the indicator variables like above):

--number_of_cols_4_wide: 4;
--number_of_cols_4_medium: 2;
--number_of_cols_4_small: 1;
--grid_gap: 0px;

--grid_columns_width_4_wide: calc(
(100% - (var(--number_of_cols_4_wide) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_wide));
--grid_columns_width_4_medium: calc(
(100% - (var(--number_of_cols_4_medium) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_medium));
--grid_columns_width_4_small: calc(
(100% - (var(--number_of_cols_4_small) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_small));

--raven_grid_columns_width: calc( /*  use the Raven to combine the values  */
  min(var(--is_wide) * var(--very_big_int),var(--grid_columns_width_4_wide)) 
  + min(var(--is_medium) * var(--very_big_int),var(--grid_columns_width_4_medium))
  + min(var(--is_small) * var(--very_big_int),var(--grid_columns_width_4_small))
  );

And set your grid up with:

.grid_container{
  display: grid;
  grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));
  gap: var(--grid_gap)
};

How does this work?

  1. Define the number of columns we want for each interval (lines 1, 2, 3)
  2. Calculate the perfect width of the columns for each interval (lines 5, 6, 7).

    What is happening here?

    First, we calculate the available space for our columns. This is 100%, minus the place the gaps will take. For n columns, there are (n-1) gaps. This space is then divided by the number of columns we want.

  3. Use the Raven to calculate the right column’s width for the actual --base_size.

In the grid container, this line:

grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));

…then chooses the number of columns to fit the value the Raven provided (which will result in our --number_of_cols_4_??? variables from above).

The Raven may not be able give the number of columns directly, but it can give a length to make repeat and autofit calculate the number we want for us.

But auto-fit with minmax() does the same thing, right? No! The solution above will never give three columns (or five) and the number of columns does not need to increase with the width of the container. Try to set the following values in this Pen to see the Raven take full flight:

--number_of_cols_4_wide: 1;
--number_of_cols_4_medium: 2;
--number_of_cols_4_small: 4;

Bonus 3: Change the background-color with a linear-gradient()

This one is a little more mind-bending. The Raven is all about length values, so how can we get a color out of these? Well, linear gradients deal with both. They define colors in certain areas defined by length values. Let’s go through that concept in more detail before getting to the code.

To work around the actual gradient part, it is a well known technique to double up a color stop, effectively making the gradient part happen within 0px. Look at this code to see how this is done:

background-image:linear-gradient(
  to right,
  red 0%,
  red 50%,
  blue 50%,
  blue 100%
);

This will color your background red on the left half, blue on the right. Note the first argument “to right.” This implies that percentage values are evaluated horizontally, from left to right.

Controlling the values of 50% via Raven variables allows for shifting the color stop at will. And we can add more color stops. In the running example, we need three colors, resulting in two (doubled) inner color stops.

Adding some variables for color and color stops, this is what we get:

background-image: linear-gradient(
  to right,
  var(--color_small) 0px,
  var(--color_small) var(--first_lgbreak_value),
  var(--color_medium) var(--first_lgbreak_value),
  var(--color_medium) var(--second_lgbreak_value),
  var(--color_wide) var(--second_lgbreak_value),
  var(--color_wide) 100%
);

But how do we calculate the values for --first_lgbreak_value and --second_lgbreak_value? Let’s see.

The first value controls where --color_small is visible. On the small interval, it should be 100%, and 0px in the other intervals. We’ve seen how to do this with the raven. The second variable controls the visibility of --color_medium. It should be 100% for the small interval, 100% for the medium interval, but 0px for the wide interval. The corresponding indicator must be 1px if the container width is in the small or the medium interval.

Since we can do boolean logic on indicators, it is:

max(--is_small, --is_medium)

…to get the right indicator. This gives:

--first_lgbreak_value: min(var(--is_small) * var(--very_big_int), 100%);
--second_lgbreak_value: min(
  max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);

Putting things together results in this CSS code to change the background-color based on the width (the interval indicators are calculated like shown above):

--first_lgbreak_value: min(
      var(--is_small) * var(--very_big_int), 100%);
--second_lgbreak_value: min(
    max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);

--color_wide: red;/* change to your needs*/
--color_medium: green;/* change to your needs*/
--color_small: lightblue;/* change to your needs*/

background-image: linear-gradient(
  to right,
  var(--color_small) 0px,
  var(--color_small) var(--first_lgbreak_value),
  var(--color_medium) var(--first_lgbreak_value),
  var(--color_medium) var(--second_lgbreak_value),
  var(--color_wide) var(--second_lgbreak_value),
  var(--color_wide) 100%
);

Here’s a Pen to see that in action.

Bonus 4: Getting rid of nested variables

While working with the Raven, I came across a strange problem: There is a limit on the number of nested variables that can be used in calc(). This can cause some problems when using too many breakpoints. As far as I understand, this limit is in place to prevent page blocking while calculating the styles and allow for faster circle-reference checks.

In my opinion, something like evaluate to value would be a great way to overcome this. Nevertheless, this limit can give you a headache when pushing the limits of CSS. Hopefully this problem will be tackled in the future.

There is a way to calculate the indicator variables for the Raven without the need of (deeply) nested variables. Let’s look at the original calculation for the --is_medium value:

--is_medium:calc(
  clamp(0px, 
        var(--base_size) - var(--breakpoint_medium), 
        1px) 
        - var(--is_wide)
); 

The problem occurs with the subtraction of --is_wide . This causes the CSS parser to paste in the definition of the complete formula of --is_wide. The calculation of --is_small has even more of these types of references. (The definition for --is_wide will even be pasted twice since it is hidden within the definition of --is_medium and is also used directly.)

Fortunately, there is a way to calculate indicators without referencing indicators for bigger breakpoints.

The indicator is true if, and only if, --base_size is bigger than the lower breakpoint for the interval and smaller or equal than the higher breakpoint for the interval. This definition gives us the following code:

--is_medium: 
  min(
    clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px),
    clamp(0px, 1px + var(--breakpoint_wide) - var(--base_size), 1px)
  );
  • min() is used as a logical AND operator
  • the first clamp() is “--base_size is bigger than --breakpoint_medium
  • the second clamp() means “--base_size is smaller or equal than --breakpoint_wide.”
  • Adding 1px switches from “smaller than” to “smaller or equal than.” This works, because we are dealing with whole (pixel) numbers (a <= b means a < (b+1) for whole numbers).

The complete calculation of the indicator variables can be done this way:

--is_wide: clamp(0px, var(--base_size) - var(--breakpoint_wide), 1px);

--is_medium: min(clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px),
                 clamp(0px, 1px + var(--breakpoint_wide) - var(--base_size), 1px)
             );

--is_small: clamp(0px,1px + var(--breakpoint_medium) - var(--base_size), 1px);

The calculations for --is_wide and --is_small are simpler, because only one given breakpoint needs to be checked for each.

This works with all the things we’ve looked at so far. Here’s a Pen that combines examples.

Final thoughts

The Raven is not capable of all the things that a media query can do. But we don’t need it to do that, as we have media queries in CSS. It is fine to use them for the “big” design changes, like the position of a sidebar or a reconfiguration of a menu. Those things happen within the context of the full viewport (the size of the browser window).

But for components, media queries are kind of wrong, since we never know how components will be sized.

Heydon Pickering demonstrated this problem with this image:

Three boxes representing browsers from left-to-right. The first is a wide viewport with three boxes in a single row. The second is a narrow viewport with the boxes stacked vertically. The third is a wide viewport, but with a dashed vertical line down the middle representing a container and the three boxes are to the right of it in a single row.

I hope that the Raven helps you to overcome the problems of creating responsive layouts for components and pushes the limits of “what can be done with CSS” a little bit further.

By showing what is possible today, maybe “real” container queries can be done by adding some syntax sugar and some very small new functions (like cw, ch, “strip-unit” or “evaluate-to-pixels”). If there was a function in CSS that allows to rewrite “1px” to a whitespace, and “0px” to “initial“, the Raven could be combined with the Custom Property Toggle Trick and change every CSS property, not just length values.

By avoiding JavaScript for this, your layouts will render faster because it’s not dependent on JavaScript downloading or running. It doesn’t even matter if JavaScript is disabled. These calculations will not block your main thread and your application logic isn’t cluttered with design logic.


Thanks to Chris, Andrés Galante, Cathy Dutton, Marko Ilic, and David Atanda for their great CSS-Tricks articles. They really helped me explore what can be done with the Raven.


The post The Raven Technique: One Step Closer to Container Queries appeared first on CSS-Tricks.

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

Animating Number Counters

Number animation, as in, imagine a number changing from 1 to 2, then 2 to 3, then 3 to 4, etc. over a specified time. Like a counter, except controlled by the same kind of animation that we use for other design animation on the web. This could be useful when designing something like a dashboard, to bring a little pizazz to the numbers. Amazingly, this can now be done in CSS without much trickery. You can jump right to the new solution if you like, but first let’s look at how we used to do it.

One fairly logical way to do number animation is by changing the number in JavaScript. We could do a rather simple setInterval, but here’s a fancier answer with a function that accepts a start, end, and duration, so you can treat it more like an animation:

Keeping it to CSS, we could use CSS counters to animate a number by adjusting the count at different keyframes:

Another way would be to line up all the numbers in a row and animate the position of them only showing one at a time:

Some of the repetitive code in these examples could use a preprocessor like Pug for HTML or SCSS for CSS that offer loops to make them perhaps easier to manage, but use vanilla on purpose so you can see the fundamental ideas.

The New School CSS Solution

With recent support for CSS.registerProperty and @property, we can animate CSS variables. The trick is to declare the CSS custom property as an integer; that way it can be interpolated (like within a transition) just like any other integer.

@property --num {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false;
}

div {
  transition: --num 1s;
  counter-reset: num var(--num);
}
div:hover {
  --num: 10000;
}
div::after {
  content: counter(num);
}

Important Note: At the time of this writing, this @property syntax is only supported in Chrome ( and other Chromium core browsers like Edge and Opera), so this isn’t cross-browser friendly. If you’re building something Chrome-only (e.g. an Electron app) it’s useful right away, if not, wait. The demos from above are more widely supported.

The CSS content property can be used to display the number, but we still need to use counter to convert the number to a string because content can only output <string> values.

See how we can ease the animations just like any other animation? Super cool! 

Typed CSS variables can also be used in @keyframes

One downside? Counters only support integers. That means decimals and fractions are out of the question. We’d have to display the integer part and fractional part separately somehow.

Can we animate decimals?

It’s possible to convert a decimal (e.g. --number) to an integer. Here’s how it works:

  1. Register an <integer> CSS variable ( e.g. --integer ), with the initial-value specified
  2. Then use calc() to round the value: --integer: calc(var(--number))

In this case, --number will be rounded to the nearest integer and store the result into --integer.

@property --integer {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
--number: 1234.5678;
--integer: calc(var(--number)); /* 1235 */

Sometimes we just need the integer part. There is a tricky way to do it: --integer: max(var(--number) - 0.5, 0). This works for positive numbers. calc() isn’t even required this way.

/* @property --integer */
--number: 1234.5678;
--integer: max(var(--number) - 0.5, 0); /* 1234 */

We can extract the fractional part in a similar way, then convert it into string with counter (but remember that content values must be strings). To display concatenated strings, use following syntax:

content: "string1" var(--string2) counter(--integer) ...

Here’s a full example that animates percentages with decimals:

Other tips

Because we’re using CSS counters, the format of those counters can be in other formats besides numbers. Here’s an example of animating the letters “CSS” to “YES”!

Oh and here’s another tip: we can debug the values grabbing the computed value of the custom property with JavaScript:

getComputedStyle(element).getPropertyValue('--variable')

That’s it! It’s amazing what CSS can do these days.


The post Animating Number Counters appeared first on CSS-Tricks.

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

When Sass and New CSS Features Collide

Recently, CSS has added a lot of new cool features such as custom properties and new functions. While these things can make our lives a lot easier, they can also end up interacting with preprocessors, like Sass, in funny ways.

So this is going to be a post about the issues I’ve encountered, how I go around them, and why I still find Sass necessary these days.

The errors

If you’ve played with the new min() and max() functions, you may have ran into an error message like this when working with different units: “Incompatible units: vh and em.”

Screenshot. Shows the `Incompatible units: 'em' and 'vh'` error when trying to set `width: min(20em, 50vh)`.
An error when working with different types of units in the min()/ max() function

This is because Sass has its ownmin() function, and ignores the CSS min() function. Plus, Sass cannot perform any sort of computation using two values with units that don’t have a fixed relation between them.

For example, cm and in units have a fixed relation between them, so Sass can figure out what’s the result of min(20in, 50cm) and doesn’t throw an error when we try to use it in our code.

The same things goes for other units. Angular units, for example, all have a fixed relation between them: 1turn, 1rad or 1grad always compute to the same deg values. Same goes for 1s which is always 1000ms, 1kHz which is always 1000Hz, 1dppx which is always 96dpi, and 1in which is always 96px. This is why Sass can convert between them and mix them in computations and inside functions such as its own min() function.

But things break when these units don’t have a fixed relation between them (like the earlier case with em and vh units).

And it’s not just different units. Trying to use calc() inside min() also results in an error. If I try something like calc(20em + 7px), the error I get is, “calc(20em + 7px) is not a number for min.”

Screenshot. Shows the `'calc(20em + 7px)' is not a number for 'min'` error when trying to set `width: min(calc(20em + 7px), 50vh)`.
An error when using different unit values with calc() nested in the min()function

Another problem arises when we want to use a CSS variable or the result of a mathematical CSS function (such as calc(), min() or max()) in a CSS filter like invert().

In this case, we get told that “$color: 'var(--p, 0.85) is not a color for invert.”

Screenshot. Shows the `$color: 'var(--p, 0.85)' is not a color for 'invert'` error when trying to set `filter: invert(var(--p, .85))`.
var() in filter: invert() error

The same thing happens for grayscale(): “$color: ‘calc(.2 + var(--d, .3))‘ is not a color for grayscale.”

Screenshot. Shows the `$color: 'calc(.2 + var(--d, .3))' is not a color for 'grayscale'` error when trying to set `filter: grayscale(calc(.2 + var(--d, .3)))`.
calc() in filter: grayscale() error

opacity() causes the same issue: “$color: ‘var(--p, 0.8)‘ is not a color for opacity.”

Screenshot. Shows the `$color: 'var(--p, 0.8)' is not a color for 'opacity'` error when trying to set `filter: opacity(var(--p, 0.8))`.
var() in filter: opacity() error

However, other filter functions — including sepia(), blur(), drop-shadow(), brightness(), contrast() and hue-rotate()— all work just fine with CSS variables!

Turns out that what’s happening is similar to the min() and max() problem. Sass doesn’t have built-in sepia(), blur(), drop-shadow(), brightness(), contrast(), hue-rotate() functions, but it does have its own grayscale(), invert() and opacity() functions, and their first argument is a $color value. Since it doesn’t find that argument, it throws an error.

For the same reason, we also run into trouble when trying to use a CSS variable that lists at least two hsl()or hsla() values.

Screenshot. Shows the `wrong number of arguments (2 for 3) for 'hsl'` error when trying to set `color: hsl(9, var(--sl, 95%, 65%))`.
var() in color: hsl() error.

On the flip side, color: hsl(9, var(--sl, 95%, 65%)) is perfectly valid CSS and works just fine without Sass.

The exact same thing happens with the rgb()and rgba() functions.

Screenshot. Shows the `$color: 'var(--rgb, 128, 64, 64)' is not a color for 'rgba'` error when trying to set `color: rgba(var(--rgb, 128, 64, 64), .7)`.
var() in color: rgba() error.

Furthermore, if we import Compass and try to use a CSS variable inside a linear-gradient() or inside a radial-gradient(), we get another error, even though using variables inside conic-gradient() works just fine (that is, if the browser supports it).

Screenshot. Shows the At least two color stops are required for a linear-gradient error when trying to set background: linear-gradient(var(--c, pink), gold).
var() in background: linear-gradient() error.

This is because Compass comes with linear-gradient() and radial-gradient() functions, but has never added a conic-gradient() one.

The problems in all of these cases arise from Sass or Compass having identically-named functions and assuming those are what we intended to use in our code.

Drat!

The solution

The trick here is to remember that Sass is case-sensitive, but CSS isn’t.

That means we can write Min(20em, 50vh)and Sass won’t recognize it as its own min() function. No errors will be thrown and it’s still valid CSS that works as intended. Similarly, writing HSL()/ HSLA()/ RGB()/ RGBA() or Invert() allows us to avoid issues we looked at earlier.

As for gradients, I usually prefer linear-Gradient() and radial-Gradient() just because it’s closer to the SVG version, but using at least one capital letter in there works just fine.

But why?

Almost every time I tweet anything Sass-related, I get lectured on how it shouldn’t be used now that we have CSS variables. I thought I’d address that and explain why I disagree.

First, while I find CSS variables immensely useful and have used them for almost everything for the past three years, it’s good to keep in mind that they come with a performance cost and that tracing where something went wrong in a maze of calc() computations can be a pain with our current DevTools. I try not to overuse them to avoid getting into a territory where the downsides of using them outweigh the benefits.

Screenshot. Shows how `calc()` expressions are presented in DevTools.
Not exactly easy to figure out what’s the result of those calc() expressions.

In general, if it acts like a constant, doesn’t change element-to-element or state-to-state (in which case custom properties are definitely the way to go) or reduce the amount of compiled CSS (solving the repetition problem created by prefixes), then I’m going to use a Sass variable.

Secondly, variables have always been a pretty small portion of why I use Sass. When I started using Sass in late 2012, it was primarily for looping, a feature we still don’t have in CSS. While I’ve moved some of that looping to an HTML preprocessor (because it reduces the generated code and avoids having to modify both the HTML and the CSS later), I still use Sass loops in plenty of cases, like generating lists of values, stop lists inside gradient functions, lists of points inside a polygon function, lists of transforms, and so on.

Here’s an example. I used to generate n HTML items with a preprocessor. The choice of preprocessor matters less, but I’ll be using Pug here.

- let n = 12;

while n--
  .item

Then I would set the $n variable into the Sass (and it would have to be equal to that in the HTML) and loop up to it to generate the transforms that would position each item:

$n: 12;
$ba: 360deg/$n;
$d: 2em;

.item {
  position: absolute;
  top: 50%; left: 50%;
  margin: -.5*$d;
  width: $d; height: $d;
  /* prettifying styles */

  @for $i from 0 to $n {
    &:nth-child(#{$i + 1}) {
      transform: rotate($i*$ba) translate(2*$d) rotate(-$i*$ba);
			
      &::before { content: '#{$i}' }
    }
  }
}

However, this meant that I would have to change both the Pug and the Sass when changing the number of items, making the generated code very repetitive.

Screenshot. Shows the generated CSS, really verbose, almost completely identical transform declaration repeated for each item.
CSS generated by the above code

I have since moved to making Pug generate the indices as custom properties and then use those in the transform declaration.

- let n = 12;

body(style=`--n: ${n}`)
  - for(let i = 0; i < n; i++)
    .item(style=`--i: ${i}`)
$d: 2em;

.item {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -.5*$d;
  width: $d;
  height: $d;
  /* prettifying styles */
  --az: calc(var(--i)*1turn/var(--n));
  transform: rotate(var(--az)) translate(2*$d) rotate(calc(-1*var(--az)));
  counter-reset: i var(--i);
	
  &::before { content: counter(i) }
}

This significantly reduces the generated code.

Screenshot. Shows the generated CSS, much more compact, no having almost the exact same declaration set on every element separately.
CSS generated by the above code

However, looping in Sass is still necessary if I want to generate something like a rainbow.

@function get-rainbow($n: 12, $sat: 90%, $lum: 65%) {
  $unit: 360/$n;
  $s-list: ();
	
  @for $i from 0 through $n {
    $s-list: $s-list, hsl($i*$unit, $sat, $lum)
  }
	
  @return $s-list
}

html { background: linear-gradient(90deg, get-rainbow()) }

Sure, I could generate it as a list variable from Pug, but doing so doesn’t take advantage of the dynamic nature of CSS variables and it doesn’t reduce the amount of code that gets served to the browser, so there’s no benefit coming out of it.

Another big part of my Sass (and Compass) use is tied to built-in mathematical functions (such as trigonometric functions), which are part of the CSS spec now, but not yet implemented in any browser. Sass doesn’t come with these functions either, but Compass does and this is why I often need to use Compass.

And, sure, I could write my own such functions in Sass. I did resort to this in the beginning, before Compass supported inverse trigonometric functions. I really needed them, so I wrote my own based on the Taylor series. But Compass provides these sorts of functions nowadays and they are better and more performant than mine.

Mathematical functions are extremely important for me as I’m a technician, not an artist. The values in my CSS usually result from mathematical computations. They’re not magic numbers or something used purely for aesthetics. A example is generating lists of clip paths points that create regular or quasi-regular polygons. Think about the case where we want to create things like non-rectangular avatars or stickers.

Let’s consider a regular polygon with vertices on a circle with a radius 50% of the square element we start from. Dragging the slider in the following demo allows us to see where the points are placed for different numbers of vertices:

Putting it into Sass code, we have:

@mixin reg-poly($n: 3) {
  $ba: 360deg/$n; // base angle
  $p: (); // point coords list, initially empty
	
  @for $i from 0 to $n {
    $ca: $i*$ba; // current angle
    $x: 50%*(1 + cos($ca)); // x coord of current point
    $y: 50%*(1 + sin($ca)); // y coord of current point
    $p: $p, $x $y // add current point coords to point coords list
  }
	
  clip-path: polygon($p) // set clip-path to list of points
}

Note that here we’re also making use of looping and of things such as conditionals and modulo that are a real pain when using CSS without Sass.

A slightly more evolved version of this might involve rotating the polygon by adding the same offset angle ($oa) to the angle of each vertex. This can be seen in the following demo. This example tosses in a star mixin that works in a similar manner, except we always have an even number of vertices and every odd-indexed vertex is situated on a circle of a smaller radius ($f*50%, where $f is sub-unitary):

We can also have chubby stars like this:

Or stickers with interesting border patterns. In this particular demo, each sticker is created with a single HTML element and the border pattern is created with clip-path, looping and mathematics in Sass. Quite a bit of it, in fact.

Another example are these card backgrounds where looping, the modulo operation and exponential functions work together to generate the dithering pixel background layers:

This demo just happens to rely heavily on CSS variables as well.

Then there’s using mixins to avoid writing the exact same declarations over and over when styling things like range inputs. Different browsers use different pseudo-elements to style the components of such a control, so for every component, we have to set the styles that control its look on multiple pseudos.

Sadly, as tempting as it may be to put this in our CSS:

input::-webkit-slider-runnable-track, 
input::-moz-range-track, 
input::-ms-track { /* common styles */ }

…we cannot do it because it doesn’t work! The entire rule set is dropped if even one of the selectors isn’t recognized. And since no browser recognises all three of the above, the styles don’t get applied in any browser.

We need to have something like this if we want our styles to be applied:

input::-webkit-slider-runnable-track { /* common styles */ }
input::-moz-range-track { /* common styles */ }
input::-ms-track { /* common styles */ }

But that can mean a lot of identical styles repeated three times. And if we want to change, say, the background of the track, we need to change it in the ::-webkit-slider-runnable-track styles, in the ::-moz-range-track styles and in the ::-ms-track styles.

The only sane solution we have is to use a mixin. The styles get repeated in the compiled code because they have to be repeated there, but we don’t have to write the same thing three times anymore.

@mixin track() { /* common styles */ }

input {
  &::-webkit-slider-runnable-track { @include track }
  &::-moz-range-track { @include track }
  &::-ms-track { @include track }
}

The bottom line is: yes, Sass is still very much necessary in 2020.

The post When Sass and New CSS Features Collide appeared first on CSS-Tricks.

Creating Color Themes With Custom Properties, HSL, and a Little calc()

Before the advent of CSS custom properties (we might call them “variables” in this article as that’s the spirit of them), implementing multiple color schemes on the same website usually meant writing separate stylesheets. Definitely not the most maintainable thing in the world. Nowadays, though, we can define variables in a single stylesheet and let CSS do the magic.

Even if you aren’t offering something like user-generated or user-chosen color themes, you might still use the concept of theming on your website. For example, it is fairly common to use different colors themes across different areas of the site.

We’re going to build out an example like this:

Same layout, different colors.

In this example, all that changes between sections is the color hue; the variations in lightness are always the same. Here’s an example of a simplified color palette for a specific hue:

A palette of multiple hues might look something like this:

This would take effort to do with RGB color value, but in HSL only one value changes.

Enter custom properties

Custom properties have been around for a while and are widely supported. Polyfills and other solutions for IE 11 are also available.

The syntax is very similar to traditional CSS. Here is an overview of the basic usage:

It’s common to see variables defined on the :root pseudo-element, which is always <html> in HTML, but with higher specificity. That said, variables can be defined on any element which is useful for scoping specific variables to specific elements. For example, here are variables defined on data attributes:

Adding calc() to the mix

Variables don’t have to be fixed values. We can leverage the power of the calc() function to automatically calculate values for us while adhering to a uniform pattern:

Since CSS doesn’t support loops, a preprocessor would be handy to generate a part of the code. But remember: CSS variables are not the same as Sass variables.

Implementing CSS variables

What we’re basically trying to do is change the color of the same component on different sections of the same page. Like this:

We have three sections in tabs with their own IDs: #food, #lifestyle, and #travel. Each section corresponds to a different hue. The  data-theme-attribute on the div.wrapper element defines which hue is currently in use.

When #travel is the active tab, we’re using the --first-hue variable, which has a value of 180°. That is what gets used as the --hue value on the section, resulting in a teal color:

<div class="wrapper" data-theme="travel">
.wrapper[data-theme="travel"] {
  --hue: var(--first-hue);  /* = 180° = teal */
}

Clicking any of the tabs updates the data-theme attribute to the ID of the section, while removing the hash (#) from it. This takes a smidge of JavaScript. That’s one of the (many) nice things about CSS: they can be accessed and manipulated with JavaScript. This is a far cry from preprocessor variables, which compile into values at the build stage and are no longer accessible in the DOM.

<li><a href="#food">Food</a></li>
const wrapper = document.querySelector('.wrapper');
document.querySelector("nav").addEventListener('click', e => {
  e.preventDefault();
  e.stopPropagation();
  // Get theme name from URL and ditch the hash
  wrapper.dataset.theme = e.target.getAttribute('href').substr(1);
})

Progressive enhancement

When we use JavaScript, we should be mindful of scenarios where a user may have disabled it. Otherwise, our scripts — and our UI by extension — are inaccessible. This snippet ensures that the site content is still accessible, even in those situations:

document.querySelectorAll('section').forEach((section, i) => {
  if (i) { // hide all but the first section
    section.style.display = 'none';
  }
})

This merely allows the tabs to scroll up the page to the corresponding section. Sure, theming is gone, but providing content is much more important.

While I chose to go with a single-page approach, it’s also possible to serve the sections as separate pages and set [data-theme] on the server side. 

Another approach

So far, we’ve assumed that color values change linearly and are thus subject to a mathematical approach. But even in situations where this is only partially true, we may still be able to benefit from the same concept. For instance, if lightness follows a pattern but hue doesn’t, we could split up the stylesheet like this:

<head>
  <style>
    :root {
      --hue: 260;
    }
  </style>
  <link rel="stylesheet" href="stylesheet-with-calculations-based-on-any-hue.css">
</head>

Supporting web components

Web components are an exciting (and evolving) concept. It’s enticing to think we can have encapsulated components that can be reused anywhere and theme them on a case-by-case basis. One component with many contexts!

We can use CSS variable theming with web components. It requires us to use a host-context() pseudo-selector. (Thanks to habemuscode for pointing this out to me!)

:host-context(body[data-theme="color-1"]) {
  --shade-1: var(--outsideHSL);
}

In summary…

Theming a website with CSS custom properties is much easier than the workaround approaches we’ve resorted to in the past. It’s more maintainable (one stylesheet), performant (less code), and opens up new possibilities (using JavaScript). Not to mention, CSS custom properties become even more powerful when they’re used with HSL colors and the calc() function.

We just looked at one example where we can change the color theme of a component based on the section where it is used. But again, there is much more opportunity here when we start to get into things like letting users change themes themselves – a topic that Chris explores in this article.

The post Creating Color Themes With Custom Properties, HSL, and a Little calc() appeared first on CSS-Tricks.

A Complete Guide to calc() in CSS

CSS has a special calc() function for doing basic math. Here's an example:

.main-content {
  /* Subtract 80px from 100vh */
  height: calc(100vh - 80px);
}

In this guide, let's cover just about everything there is to know about this very useful function.

calc() is for values

The only place you can use the calc() function is in values. See these examples where we're setting the value for a number of different properties.

.el {
  font-size: calc(3vw + 2px);
  width:     calc(100% - 20px);
  height:    calc(100vh - 20px);
  padding:   calc(1vw + 5px);
}

It could be used for only part of a property too, for example:

.el {
  margin: 10px calc(2vw + 5px);
  border-radius: 15px calc(15px / 3) 4px 2px;
  transition: transform calc(1s - 120ms);
}

It can even be a part of another function that forms a part of a property! For example, here's calc() used within the color stops of a gradient

.el {
  background: #1E88E5 linear-gradient(
    to bottom,
    #1E88E5,
    #1E88E5 calc(50% - 10px),
    #3949AB calc(50% + 10px),
    #3949AB
  );
}

calc() is for lengths

Notice all the examples above are essentially numbers-based. We'll get to some of the caveats of how the numbers can be used (because sometimes you don't need a unit), but this is for number math, not strings or anything like that.

.el {
  /* Nope! */
  counter-reset: calc("My " + "counter");
}
.el::before {
  /* Nope! */
  content: calc("Candyman " * 3);
}

There are many lengths of CSS though, and they can all be used with calc():

  • px
  • %
  • em
  • rem
  • in
  • mm
  • cm
  • pt
  • pc
  • ex
  • ch
  • vh
  • vw
  • vmin
  • vmax

You can also not perform any calculation and it is still valid:

.el {
  /* Little weird but OK */
  width: calc(20px);
}

Nope on media queries

When calc() is used correctly (length units used as a value to a property), it sadly calc() won't work when applied to media queries.

@media (max-width: 40rem) {
  /* Narrower or exactly 40rem */
}

/* Nope! */
@media (min-width: calc(40rem + 1px)) {
  /* Wider than 40rem */
}

It would be cool someday because you could do mutually exclusive media queries in a fairly logical way (like above).

Mixing units 🎉

This is perhaps the most valuable feature of calc()! Almost every example above has already done this, but just to put a point on it, here it is mixing different units:

/* Percentage units being mixed with pixel units */
width: calc(100% - 20px);

That's saying: As wide as the element is, minus 20 pixels.

There is literally no way to pre-calculate that value in pixels alone in a fluid width situation. In other words, you can't preprocess calc() with something like Sass as an attempted complete a polyfill. Not that you need to, as the browser support is fine. But the point is that it has to be done in the browser (at "runtime") when you mix units in this way, which is most of the value of calc().

Here's some other examples of mixing units:

transform: rotate(calc(1turn + 45deg));

animation-delay: calc(1s + 15ms);

Those probably could be preprocessed as they mix units that aren't relative to anything that is determined at runtime.

Comparison to preprocessor math

We just covered that you can't preprocess the most useful things that calc() can do. But there is a smidge of overlap. For example, Sass has math built into it, so you can do things like:

$padding: 1rem;

.el[data-padding="extra"] {
  padding: $padding + 2rem; // processes to 3rem;
  margin-bottom: $padding * 2; // processes to 2rem; 
}

Even math with units is working there, adding same-unit values together or multiplying by unitless numbers. But you can't mix units and it has similar limitations to calc() (e.g. like multiplying and dividing must be with unit-less numbers).

Show the math

Even you aren't using a feature that is uniquely possible only with calc(), it can be used to "show your work" inside CSS. For example, say you need to calculate exactly 17th the width of an element...

.el {
  /* This is easier to understand */
  width: calc(100% / 7);

  /* Than this is */
  width: 14.2857142857%;
}

That might pan out in some kind of self-created CSS API like:

[data-columns="7"] .col { width: calc(100% / 7); }
[data-columns="6"] .col { width: calc(100% / 6); }
[data-columns="5"] .col { width: calc(100% / 5); }
[data-columns="4"] .col { width: calc(100% / 4); }
[data-columns="3"] .col { width: calc(100% / 3); }
[data-columns="2"] .col { width: calc(100% / 2); }

The Math operators of calc()

You've got +, -, *, and /. But they differ in how you are required to use them.

Addition (+) and subtraction (-) require both numbers to be lengths

.el {
  /* Valid 👍 */
  margin: calc(10px + 10px);

  /* Invalid 👎 */
  margin: calc(10px + 5);
}

Invalid values invalidate the whole individual declaration.

Division (/) requires the second number to be unitless

.el {
  /* Valid 👍 */
  margin: calc(30px / 3);

  /* Invalid 👎 */
  margin: calc(30px / 10px);

  /* Invalid 👎 (can't divide by 0) */
  margin: calc(30px / 0);
}

Multiplication (*) requires one of the numbers to be unitless

.el {
  /* Valid 👍 */
  margin: calc(10px * 3);

  /* Valid 👍 */
  margin: calc(3 * 10px);

  /* Invalid 👎 */
  margin: calc(30px * 3px);
}

Whitespace matters

Well, it does for addition and subtraction.

.el {
  /* Valid 👍 */
  font-size: calc(3vw + 2px);

  /* Invalid 👎 */
  font-size: calc(3vw+2px);

  /* Valid 👍 */
  font-size: calc(3vw - 2px);

  /* Invalid 👎 */
  font-size: calc(3vw-2px);
}

I imagine it has to do with negative numbers somehow, as using negative numbers (e.g. calc(5vw - -5px)) is OK. I suppose that makes something like calc(5vw--5px) weird, especially in how custom properties use double-dashes (e.g. var(--padding)).

Multiplication and division do not need the whitespace around the operators. But I'd think good general advice is to include the space for readability and muscle memory for the other operators.

Whitespace around the outsides doesn't matter. You can even do line breaks if you'd like:

.el {
  /* Valid 👍 */
  width: calc(
    100%     /   3
  );
}

Careful about this, though: no spaces between calc() and the opening paren.

.el {
  /* Invalid 👎 */
  width: calc (100% / 3);
}

Nesting calc(calc());

You can but it's never necessary. It's the same as using an extra set of parentheses without the calc() part. For example:

.el {
  width: calc(
    calc(100% / 3)
    -
    calc(1rem * 2)
  );
}

You don't need those inside calc() because the parens work alone:

.el {
  width: calc(
   (100% / 3)
    -
   (1rem * 2)
  );
}

And in this case, the "order of operations" helps us even without the parentheses. That is, division and multiplication happen first (before addition and subtraction), so the parentheses aren't needed at all. It could be written like this:

.el {
  width: calc(100% / 3 - 1rem * 2);
}

But feel free to use the parens if you feel like it adds clarity. If the order of operations doesn't work in your favor (e.g. you really need to do the addition or subtraction first), you'll need parens.

.el {
  /* This */
  width: calc(100% + 2rem / 2);

  /* Is very different from this */
  width: calc((100% + 2rem) / 2);
}

CSS custom properties and calc() 🎉

Other than the amazing ability of calc() to mix units, the next most awesome thing about calc() is using it with custom properties. Custom properties can have values that you then use in a calculation:

html {
  --spacing: 10px;
}

.module {
  padding: calc(var(--spacing) * 2);
}

I'm sure you can imagine a CSS setup where a ton of configuration happens at the top by setting a bunch of CSS custom properties and then letting the rest of the CSS use them as needed.

Custom properties can also reference each other. Here's an example where some math is used (note the lack of a calc() function at first) and then later applied. (It ultimately has to be inside of a calc().)

html {
  --spacing: 10px;
  --spacing-L: var(--spacing) * 2;
  --spacing-XL: var(--spacing) * 3;
}

.module[data-spacing="XL"] {
  padding: calc(var(--spacing-XL));
}

Custom properties can come from the HTML, which is a pretty darn cool and useful thing sometimes. (See how Splitting.js adds indexes to words/characters as an example.)

<div style="--index: 1;"> ... </div>
<div style="--index: 2;"> ... </div>
<div style="--index: 3;"> ... </div>
div {
  /* Index value comes from the HTML (with a fallback) */
  animation-delay: calc(var(--index, 1) * 0.2s);
}

Adding units later

In case you're in a situation where it's easier to store numbers without units, or do math with unit-less numbers ahead of time, you can always wait until you apply the number to add the unit by multiplying by 1 and the unit.

html {
  --importantNumber: 2;
}

.el {
  /* Number stays 2, but it has a unit now */
  padding: calc(var(--importantNumber) * 1rem);
}

Messing with colors

Color format like RGB and HSL have numbers you can mess with using calc(). For example, setting some base HSL values and then altering them forming a system of your own creation (example):

html {
  --H: 100;
  --S: 100%;
  --L: 50%;
}

.el {
  background: hsl(
    calc(var(--H) + 20),
    calc(var(--S) - 10%),
    calc(var(--L) + 30%)
  )
}

You can't combine calc() and attr()

The attr() function in CSS looks appealing, like you can yank attribute values out of HTML and use them. But...

<div data-color="red">...</div>
div {
  /* Nope */
  color: attr(data-color);
}

Unfortunately, there are no "types" in play here, so the only thing attr() is for are strings in conjunction with the content property. That means this works:

div::before {
  content: attr(data-color);
}

I mention this, because it might be tempting to try to pull a number in that way to use in a calculation, like:

<div class="grid" data-columns="7" data-gap="2">...</div>
.grid {
  display: grid;

  /* Neither of these work */
  grid-template-columns: repeat(attr(data-columns), 1fr);
  grid-gap: calc(1rem * attr(data-gap));
}

Fortunately, it doesn't matter much because custom properties in the HTML are just as useful or more!

<div class="grid" style="--columns: 7; --gap: 2rem;">...</div>
.grid {
  display: grid;

  /* Yep! */
  grid-template-columns: repeat(var(--columns), 1fr);
  grid-gap: calc(var(--gap));
}

Browser tooling

Browser DevTools will tend you show you the calc() as you authored it in the stylesheet.

Firefox DevTools - Rules

If you need to figure out the computed value, there is a Computed tab (in all browser DevTools, at least that I know about) that will show it to you.

Chrome DevTools - Computed

Browser support

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

Desktop

ChromeFirefoxIEEdgeSafari
19*4*11126*

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
8068806.0-6.1*

If you really needed to support super far back (e.g. IE 8 or Firefox 3.6), the usual trick is to add another property or value before the one that uses calc():

.el {
  width: 92%; /* Fallback */
  width: calc(100% - 2rem);
}

There are quite a few known issues for calc() as well, but they are all for old browsers. Can I use... lists 13 of them, here's a handful:

  • Firefox <59 does not support calc() on color functions. Example: color: hsl(calc(60 * 2), 100%, 50%).
  • IE 9 - 11 will not render the box-shadow property when calc() is used for any of the values.
  • Neither IE 9 - 11 nor Edge support width: calc() on table cells.

Use-case party

I asked some CSS developers when they last used calc() so we could have a nice taste here for for how others use it in their day-to-day work.

I used it to create a full-bleed utility class: .full-bleed { width: 100vw; margin-left: calc(50% - 50vw); } I'd say calc() is in my top 3 CSS things.

I used it to make space for a sticky footer.

I used it to set some fluid type / dynamic typography... a calculated font-size based on minimums, maxiums, and a rate of change from viewport units. Not just the font-size, but line-height too.

If you're using calc() as part of a fluid type situation that involves viewport units and such, make sure that you include a unit that uses rem or em so that the user still has some control over bumping that font up or down by zooming in or out as they need to.

One I really like is having a "content width" custom property and then using that to create the spacing that I need, like margins: .margin { width: calc( (100vw - var(--content-width)) / 2); }

I used it to create a cross-browser drop-cap component. Here's a part of it:

.drop-cap { --drop-cap-lines: 3; font-size: calc(1em * var(--drop-cap-lines) * var(--body-line-height)); }

I used it to make some images overflow their container on an article page.

I used it to place a visualization correctly on the page by combining it with padding and vw/vh units.

I use it to overcome limitations in background-position, but expecially limitations in positioning color stops in gradients. Like "stop 0.75em short of the bottom".

Other Trickery

The post A Complete Guide to calc() in CSS appeared first on CSS-Tricks.

Currying in CSS

Funny timing on this I was just looking at the website for Utopia (which is a responsive type project which I hate to admit I don't fully understand) and I came across some CSS they show off that looked like this:

:root {
  --fluid-max-negative: (1 / var(--fluid-max-ratio) / var(--fluid-max-ratio));
  --fluid-min-negative: (1 / var(--fluid-min-ratio) / var(--fluid-min-ratio));
 
  ...
}

See anything weird there? That code is using mathematical operators, but there is no calc() function wrapped around it.

Just as my curiosity set in, Trys Mudford, a creator of Utopia, blogged it:

The value after the : in the CSS custom property does not have to be valid CSS. It won’t cause any errors, nor invalidate the custom property. It won’t be evaluated in the browser until used, or more specifically, placed in a calc() function.

Here's a contrived example:

:root {
  --padding: 1rem;
  
  /* These are meaningless alone */
  --padding-S: var(--padding) / 2;
  --padding-L: var(--padding) * 2;
}

.module--large {
  /* But they evaluate once they are in a calc() */
  padding: calc(var(--padding-L));
}

In my limited understanding, currying is like functions that return functions. I suppose this is sorta like that in that the alternate padding properties above are sort of like derivative functions of the main padding function (if you can call it that), and you only call them and execute them as needed.

The post Currying in CSS appeared first on CSS-Tricks.

Responsive Designs and CSS Custom Properties: Defining Variables and Breakpoints

CSS custom properties (a.k.a. CSS variables) are becoming more and more popular. They finally reached decent browser support and are slowly making their way into various production environments. The popularity of custom properties shouldn’t come as a surprise, because they can be really helpful in numerous use cases, including managing color palettes, customizing components, and theming. But CSS variables can also be really helpful when it comes to responsive design.

Article Series:

  1. Defining Variables and Breakpoints (This Post)
  2. Building a Flexible Grid System (Coming Tomorrow!)

Let’s consider an <article> element with a heading and a paragraph inside:

<article class="post">
	<h2 class="heading">Post's heading</h2>
	<p class="paragraph">
		Lorem ipsum dolor sit amet, consectetur adipisicing elit.
		Laudantium numquam adipisci recusandae officiis dolore tenetur,
		nisi, beatae praesentium, soluta ullam suscipit quas?
	</p>
</article>

It’s a common scenario in such a case to change some sizes and dimensions depending on the viewport’s width. One way to accomplish this is by using media queries:

.post {
	padding: 0.5rem 1rem;
	margin: 0.5rem auto 1rem;
}

.heading {
	font-size: 2rem;
}

@media (min-width: 576px) {
	.post {
		padding: 1rem 2rem;
		margin: 1rem auto 2rem;
	}
	
	.heading {
		font-size: 3rem;
	}
}

See the Pen
#1 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

Such an approach gives us an easy way to control CSS properties on different screen sizes. However, it may be hard to maintain as the complexity of a project grows. When using media queries, keeping code readable and DRY at the same time quite often turns out to be challenging.

The most common challenges when scaling this pattern include:

  • Repeated selectors: Apart from bloating code with multiple declarations, it also makes future refactoring more difficult, e.g. every time a class name changes it requires remembering to update it in multiple places.
  • Repeated properties: Notice that when overwriting CSS rules within media queries, it requires repeating the entire declaration (e.g. font-size: 3rem;) even though it’s just the value (3rem) that actually changes.
  • Repeated media queries: To keep responsive styles contextual, it’s a common practice to include the same media queries in multiple places, close to the styles they override. Unfortunately, it not only makes code heavier, but also might make breakpoints much harder to maintain. On the other hand, keeping all responsive styles in one place, away from their original declarations, may be very confusing: we end up with multiple references to the same elements sitting in completely different places.

We can argue that repeated declarations and queries shouldn’t be such a big deal with proper file compression enabled, at least as long as we’re referring to performance. We can also merge multiple queries and optimize your code with post-processing tools. But wouldn’t it be easier to avoid these issues altogether?

There’s a lot of ways to avoid the issues listed above. One of them, that we will explore in this article, is to use CSS custom properties.

Using CSS variables for property values

There are plenty of amazing articles on the web explaining the concept of CSS custom properties. If you haven’t got chance to get familiar with them yet, I would recommend starting with one of the beginner articles on this topic such as this awesome piece by Serg Hospodarets as we are not going to get into details of the basic usage in this article.

The most common way of utilizing CSS custom properties in responsive design is to use variables to store values that change inside of media queries. To accomplish this, declare a variable that holds a value that is supposed to change, and then reassign it inside of a media query:

:root {
  --responsive-padding: 1rem;
}

@media (min-width: 576px) {                             
  :root {
    --responsive-padding: 2rem;
  }
}

.foo {
	padding: var(--responsive-padding);
}

Assigning variables to the :root selector is not always a good idea. Same as in JavaScript, having many global variables is considered a bad practice. In real life, try to declare the custom properties in the scope they will actually be used.

This way, we are avoiding multiple rules of the .foo class. We are also separating the logic (changing values) from the actual designs (CSS declarations). Adapting this approach in our example from above gives us the following CSS:

.post {
	--post-vertical-padding: 0.5rem;
	--post-horizontal-padding: 1rem;
	--post-top-margin: 0.5rem;
	--post-bottom-margin: 1rem;
	--heading-font-size: 2rem;
}

@media (min-width: 576px) {
	.post {
		--post-vertical-padding: 1rem;
		--post-horizontal-padding: 2rem;
		--post-top-margin: 1rem;
		--post-bottom-margin: 2rem;
		--heading-font-size: 3rem;
	}
}

.post {
	padding: var(--post-vertical-padding) var(--post-horizontal-padding);
	margin: var(--post-top-margin) auto  var(--post-bottom-margin);
}

.heading {
	font-size: var(--heading-font-size);
}

See the Pen
#2 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

Notice that the use of variables in shorthand properties (e.g. padding, margin or font) allow some very interesting repercussions. As custom properties may hold almost any value (more on this later), even an empty string, it’s unclear how the value of a shorthand property will be separated out into longhand properties that are used in the cascade later. For example, the auto used in the margin property above may turn out to be a top-and-bottom margin, a left-and-right margin, a top margin, a right margin, a bottom margin or a left margin — it all depends on the values of the custom properties around.

It’s questionable whether the code looks cleaner than the one from the previous example, but on a larger scale, it’s definitely more maintainable. Let’s try to simplify this code a bit now.

Notice that some values are repeated here. What if we try to merge duplicate variables together? Let’s consider the following alteration:

:root {
	--small-spacing: 0.5rem;
	--large-spacing: 1rem;
	--large-font-size: 2rem;
}

@media (min-width: 576px) {
	:root {
		--small-spacing: 1rem;
		--large-spacing: 2rem;
		--large-font-size: 3rem;
	}
}

.post {
	padding: var(--small-spacing) var(--large-spacing);
	margin: var(--small-spacing) auto  var(--large-spacing);
}

.heading {
	font-size: var(--large-font-size);
}

See the Pen
#3 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

It looks cleaner but is it actually better? Not necessarily. For the sake of flexibility and readability, this may not be the right solution in every case. We definitely shouldn’t merge some variables just because they accidentally turned out to hold the same values. Sometimes, as long as we’re doing this as a part of a well thought out system, it may help us simplify things and preserve consistency across the project. However, in other cases, such a manner may quickly prove to be confusing and problematic. Now, let’s take a look at yet another way we can approach this code.

Using CSS variables as multipliers

CSS custom properties are a fairly new feature to the modern web. One of the other awesome features that rolled out in the last years is the calc() function. It lets us perform real math operations in live CSS. In terms of the browser support, it’s supported in all browsers that support CSS custom properties.

calc() tends to play very nicely with CSS variables, making them even more powerful. This means we can both use calc() inside custom properties and custom properties inside calc()!

For example, the following CSS is perfectly valid:

:root {
	--size: 2;
}
	
.foo {
	--padding: calc(var(--size) * 1rem); /* 2 × 1rem = 2rem */
	padding: calc(var(--padding) * 2);   /* 2rem × 2 = 4rem */
}

Why does this matter to us and our responsive designs? It means that we can use a calc() function to alter CSS custom properties inside media queries. Let’s say we have a padding that should have a value of 5px on mobile and 10px on desktop. Instead of declaring this property two times, we can assign a variable to it and multiply it by two on larger screens:

:root {
	--padding: 1rem;
	--foo-padding: var(--padding);
}

@media (min-width: 576px) {                             
	:root {
		--foo-padding: calc(var(--padding) * 2);
	}
}

.foo {
	padding: var(--foo-padding);
}

Looks fine, however all the values (--padding, calc(--padding * 2)) are away from their declaration (padding). The syntax may also be pretty confusing with two different padding variables (--padding and --foo-padding) and an unclear relationship between them.

To make things a bit clearer, let’s try to code it the other way around:

:root {
	--multiplier: 1;
}

@media (min-width: 576px) {                             
	:root {
		--multiplier: 2;
	}
}

.foo {
	padding: calc(1rem * var(--multiplier));
}

This way, we accomplished the same computed output with much cleaner code! So, instead of using a variable for an initial value of the property (1rem), a variable was used to store a multiplier (1 on small screens and 2 on larger screens). It also allows us to use the --multiplier variable in other declarations. Let’s apply this technique to paddings and margins in our previous snippet:

:root {
	--multiplier: 1;
}

@media (min-width: 576px) {
	:root {
		--multiplier: 2;
	}
}

.post {
	padding: calc(.5rem * var(--multiplier))
						calc(1rem  * var(--multiplier));
	margin:  calc(.5rem * var(--multiplier))
						auto
						calc(1rem  * var(--multiplier));
}

Now, let’s try to implement the same approach with typography. First, we’ll add another heading to our designs:

<h1 class="heading-large">My Blog</h1>
<article class="post">
	<h2 class="heading-medium">Post's heading</h2>
	<p class="paragraph">
		Lorem ipsum dolor sit amet, consectetur adipisicing elit.
		Laudantium numquam adipisci recusandae officiis dolore tenetur,
		nisi, beatae praesentium, soluta ullam suscipit quas?
	</p>
</article>

With multiple text styles in place, we can use a variable to control their sizes too:

:root {
	--headings-multiplier: 1;
}

@media (min-width: 576px) {
	:root {
		--headings-multiplier: 3 / 2;
	}
}

.heading-medium {
	font-size: calc(2rem * var(--headings-multiplier))
}

.heading-large {
	font-size: calc(3rem * var(--headings-multiplier))
}

You may have noticed that 3 / 2 is not a valid CSS value at all. Why does it not cause an error then? The reason is that the syntax for CSS variables is extremely forgiving, which means almost anything can be assigned to a variable, even if it’s not a valid CSS value for any existing CSS property. Declared CSS custom properties are left almost entirely un-evaluated until they are computed by a user agent in certain declarations. So, once a variable is used in a value of some property, this value will turn valid or invalid at the computed-value time.

Oh, and another note about that last note: in case you’re wondering, I used a value of 3 / 2 simply to make a point. In real life, it would make more sense to write 1.5 instead to make the code more readable.

Now, let’s take a look at the finished live example combining everything that we discussed above:

See the Pen
#4 Building responsive features with CSS custom properties
by Mikołaj (@mikolajdobrucki)
on CodePen.

Again, I would never advocate for combining calc() with custom properties to make the code more concise as a general rule. But I can definitely imagine scenarios in which it helps to keep code more organized and maintainable. This approach also allows the weight of CSS to be significantly reduced, when it’s used wisely.

In terms of readability, we can consider it more readable once the underlying rule is understood. It helps to explain the logic and relations between values. On the other hand, some may see it as less readable, because it’s tough to instantly read what a property holds as a value without first doing the math. Also, using too many variables and calc() functions at once may unnecessarily obscure code and make it harder to understand, especially for juniors and front-end developers who are not focused on CSS.

Conclusion

Summing up, there’s a lot of ways to use CSS custom properties in responsive design, definitely not limited to the examples shown above. CSS variables can be used simply to separate the values from the designs. They can also be taken a step further and be combined with some math. None of the presented approaches is better nor worse than the others. The sensibility of using them depends on the case and context.

Now that you know how CSS custom properties can be used in responsive design, I hope you will find a way to introduce them in your own workflow. Next up, we’re going to look at approaches for using them in reusable components and modules, so stay tuned for the next post tomorrow!

The post Responsive Designs and CSS Custom Properties: Defining Variables and Breakpoints appeared first on CSS-Tricks.

CSS Variables + calc() + rgb() = Enforcing High Contrast Colors

As you may know, the recent updates and additions to CSS are extremely powerful. From Flexbox to Grid, and — what we’re concerned about here — Custom Properties (aka CSS variables), all of which make robust and dynamic layouts and interfaces easier than ever while opening up many other possibilities we used to only dream of.

The other day, I was thinking that there must be a way to use Custom Properties to color an element's background while maintaining a contrast with the foreground color that is high enough (using either white or black) to pass WCAG AA accessibility standards.

It’s astonishingly efficient to do this in JavaScript with a few lines of code:

var rgb = [255, 0, 0];

function setForegroundColor() {
  var sum = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000);
  return (sum > 128) ? 'black' : 'white';
}

This takes the red, green and blue (RGB) values of an element’s background color, multiplies them by some special numbers (299, 587, and 144, respectively), adds them together, then divides the total by 1,000. When that sum is greater than 128, it will return black; otherwise, we’ll get white. Not too bad.

The only problem is, when it comes to recreating this in CSS, we don't have access to a native if statement to evaluate the sum. So,how can we replicate this in CSS without one?

Luckily, like HTML, CSS can be very forgiving. If we pass a value greater than 255 into the RGB function, it will get capped at 255. Same goes for numbers lower than 0. Even negative integers will get capped at 0. So, instead of testing whether our sum is greater or less than 128, we subtract 128 from our sum, giving us either a positive or negative integer. Then, if we multiply it by a large negative value (e.g. -1,000), we end up with either very large positive or negative values that we can then pass into the RGB function. Like I said earlier, this will get capped to the browser’s desired values.

Here is an example using CSS variables:

:root {
  --red: 28;
  --green: 150;
  --blue: 130;

  --accessible-color: calc(
    (
      (
        (var(--red) * 299) +
        (var(--green) * 587) +
        (var(--blue) * 114) /
        1000
      ) - 128
    ) * -1000
  );
}

.button {
  color:
    rgb(
      var(--accessible-color),
      var(--accessible-color),
      var(--accessible-color)
    );
  background-color:
    rgb(
      var(--red),
      var(--green),
      var(--blue)
    );
}

If my math is correct (and it's very possible that it's not) we get a total of 16,758, which is much greater than 255. Pass this total into the rgb() function for all three values, and the browser will set the text color to white.

At this point, everything seems to be working in both Chrome and Firefox, but Safari is a little cranky and gives a different result. At first, I thought this might be because Safari was not capping the large values I was providing in the function, but after some testing, I found that Safari didn't like the division in my calculation for some reason.

Taking a closer look at the calc() function, I noticed that I could remove the division of 1,000 by increasing the value of 128 to 128,000. Here’s how that looks so far:

:root {
  --red: 28;
  --green: 150;
  --blue: 130;

  --accessible-color: calc(
    (
      (
        (var(--red) * 299) +
        (var(--green) * 587) +
        (var(--blue) * 114)
      ) - 128000 /* HIGHLIGHT */
    ) * -1000
  );
}

.button {
  color:
    rgb(
      var(--accessible-color),
      var(--accessible-color),
      var(--accessible-color)
    );
  background-color:
    rgb(
      var(--red),
      var(--green),
      var(--blue)
    );
}

Throw in a few range sliders to adjust the color values, and there you have it: a dynamic UI element that can swap text color based on its background-color while maintaining a passing grade with WCAG AA.

See the Pen
CSS Only Accessible Button
by Josh Bader (@joshbader)
on CodePen.

Putting this concept to practical use

Below is a Pen showing how this technique can be used to theme a user interface. I have duplicated and moved the --accessible-color variable into the specific CSS rules that require it, and to help ensure backgrounds remain accessible based on their foregrounds, I have multiplied the --accessible-color variable by -1 in several places. The colors can be changed by using the controls located at the bottom-right. Click the cog/gear icon to access them.

See the Pen
CSS Variable Accessible UI
by Josh Bader (@joshbader)
on CodePen.

There are other ways to do this

A little while back, Facundo Corradini explained how to do something very similar in this post. He uses a slightly different calculation in combination with the hsl function. He also goes into detail about some of the issues he was having while coming up with the concept:

Some hues get really problematic (particularly yellows and cyans), as they are displayed way brighter than others (e.g. reds and blues) despite having the same lightness value. In consequence, some colors are treated as dark and given white text despite being extremely bright.

What in the name of CSS is going on?

He goes on to mention that Edge wasn’t capping his large numbers, and during my testing, I noticed that sometimes it was working and other times it was not. If anyone can pinpoint why this might be, feel free to share in the comments.

Further, Ana Tudor explains how using filter + mix-blend-mode can help contrast text against more complex backgrounds. And, when I say complex, I mean complex. She even goes so far as to demonstrate how text color can change as pieces of the background color change — pretty awesome!

Also, Robin Rendle explains how to use mix-blend-mode along with pseudo elements to automatically reverse text colors based on their background-color.

So, count this as yet another approach to throw into the mix. It’s incredibly awesome that Custom Properties open up these sorts of possibilities for us while allowing us to solve the same problem in a variety of ways.

The post CSS Variables + calc() + rgb() = Enforcing High Contrast Colors appeared first on CSS-Tricks.