Conical Gradients in CSS

Published by Guest Author

The following is a guest post by Shankar Cabus. Shankar made a really rad color wheel on CodePen and showed it to me. I thought it was an image underneath, because gradients like this aren't something CSS can natively do. Or so I thought. Shankar found a way to do it with newfangled tech.

When I create filters, shadows, transformations or gradient backgrounds in CSS, it feels amazing. Who would have thought CSS would come this far. No need to use images for any of that stuff. I can almost hear the browser telling me "don't worry, I do it for you."

Despite all this evolution, CSS still has limitations. For instance, background gradients can only be created in linear or radial styles, but not a conical shape.

In 2011, Lea Verou started a thread about implementing a native CSS conical gradient, creating a draft specification which has already been introduced to W3C's official draft. However, we still have to wait for the W3C to formalize the feature and for the browsers to implement it, which might still take quite some time. In the meantime, I will show you how to simulate a conical gradient using CSS3 only.

Example of a conical gradient.

Beautiful, right? Let's look at the code!

The Beginning

To reduce code duplication, I'm using Sass. One of the most interesting features of CSS preprocessors is the @mixin. A @mixin is a blend of function and include which, when called, returns its contents.

@mixin circle($size) {
  content: "";
  position: absolute;
  border-radius: 50%;
  width: $size;
  height: $size;
  left: calc(50% - #{$size/2});
  top: calc(50% - #{$size/2});
}

This @mixin is only used to set properties of shape and placement, creating a circle of absolute position and centered horizontally and vertically in relation to its parent.

The Magic!

By combining a @mixin circle with the clip property, we can get a semi-circle. Let's start by creating a full circle by placing two semi-circles (with different colors) together. Can you imagine what is going to happen if we rotate one of the semi-circles? Magic!

$wheel: 15em;
.color {
  @include circle($wheel);
  background: red;
  clip: rect(0, $wheel, $wheel, #{$wheel/2});
  &:after {
    @include circle($wheel);
    background: blue;
    clip: rect(0, #{$wheel/2}, $wheel, 0);
    transform: rotate(45deg);
  }
}

The clip: rect (top, right, bottom, left) property restricts the visible area to a rectangular element, which causes only half of the red circle to be seen in the example above. The same principle is applied to the blue circle, the .color:after element. At this point we would have a full circle which is half red and half blue. However, the transform property causes the blue circle's visible area to invade the red circle's. See example.

Semi-circles with clip property

The Colorful Umbrella

Colorful umbrella

We already know how to perform this magic trick. Let's create a 12 color umbrella:

<div class="wheel">
  <ul class="umbrella">
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
    <li class="color"></li>
  </ul>
</div>

Since we want to create a full circle by combining semi-circles, from the seventh item (first item in the second half), we must reverse the clip:

.color, .color:nth-child(n+7):after {
  clip: rect(0, $wheel, $wheel, #{$wheel/2});
}
.color:after, .color:nth-child(n+7) {
  @include circle($wheel);
  clip: rect(0, #{$wheel/2}, $wheel, 0);
}

From the seventh item, the .color becomes a semi-circle with the left half of the circle and the .color:after elements pass to the right half of the circle.

We are almost done!! We need to change the colors and the angle of each element. Again, let's abuse the power of Sass to generate 26193^42 lines of code in just over 10 ;)

$colors: (#9ED110, #50B517, #179067, #476EAF, #9f49ac, #CC42A2, #FF3BA7, #FF5800, #FF8100, #FEAC00, #FFCC00, #EDE604);
@for $i from 0 to length($colors) {
  .color:nth-child(#{1+$i}):after {
    background-color: nth($colors, $i+1);
    @if $i < 6 {
      transform: rotate(#{30*(1+$i)}deg);
      z-index: #{length($colors)-$i};
    } @else {
      transform: rotate(#{-30+(30*(1+$i))}deg);
    }
  }
}

First, we define the $colors array with the "12 colors of the rainbow" and then iterate over the list creating .color:nth-child(n):after selector with the background-color, rotate and z-index properties.

The rotate property has some important points. Its angle is defined according to the number of colors in the circle. In our case, there are 12 colors, so 360/12 = 30 is the rotation of each color. But from the seventh item on, the other half of the circle starts, remember? Thus, the process we just described stops at the seventh item. We will then start the very same process again, but this time, the rotation will take place on another direction.. That's why there is an @else with rotate(#{-30+(30*($i+1))}deg) which subtracts 30 degrees from the elements of the second half of the circle.

If you are a good observer (and understood everything so far), you should have noticed that our our umbrella is actually a fan! Bazinga! So for the last color of the first half of the circle does not get on top of other colors, we need to reverse the index of these elements. For example, the z-index (6) = 1 and z-index (1) = 6.

A Little More Magic

Finally, we need to ease the transition between colors, after all, we don't want an umbrella-range-rainbow, we want a conical gradient!

.umbrella {
  -webkit-filter: blur(1.7em);
}

.wheel {
  overflow: hidden;
  box-shadow: inset 0 0 0 3em rgba(0, 0, 0, 0.3);
}

The blur filter is responsible for mixing the colors. But, by applying the blur, the colors strain the limits of the circle. That's why the overflow: hidden property was also added to the .wheel element. The inner box-shadow is used to darken the edges that were "washed out". This is the end result:

See the Pen Conical gradient in pure CSS by Shankar Cabus (@shankarcabus) on CodePen

Demo

The conical gradient can be used in different ways to create different effects. But one of the most interesting applications is the Color Picker, as in the example below:

See the Pen Color Picker in Pure CSS by Shankar Cabus (@shankarcabus) on CodePen

Other Demos

Editor's note: I've seen many other approaches to this over time. I'll start dropping them here as a reference.

See the Pen css conical gradient by Kai Waddington (@waddington) on CodePen.

Another demo by Michael Wehner.