DEV Community


Posted on


Interactive, accessible circle wedges with only CSS

Alt Text

It's one of those things you're almost never asked as a front end dev - but when you are, it's a struggle to find a working example on the web.

I've searched everywhere, and found many solutions - all of which are very superficial, in other words they fill the design aspects but don't give you any usable or accessible elements in the end.

The request:

The client says, "Hey, what we really want here is a spinny wheely thing - like the one at carnivals where you can win a prize depending on where you land - but instead of spinning it, we want each wedge to represent [insert anything you like here] - is that possible?

And you say "Sure..." because it can't be that hard, right?

And then you suffer. All the solutions out there don't create clickable, accessible, style-able elements.

The solution:

[tl;dr] Codepen

In it's simplest form you create a container, and make sure it's overflow is hidden and that it's a perfect circle with border-radius: 50%

In the markup you can add the inner elements - they can be buttons, divs, a ul/ol with lis etc... It doesn't really matter what you put in there, it's the CSS calculations that count. Here's my example HTML"

<div class="pie">
    <span class="text">1</span>    
    <span class="text">2</span>
    <span class="text">3</span>
Enter fullscreen mode Exit fullscreen mode

In my example I named the container .pie, here's the important CSS:

.pie {
  border-radius: 50%;
  height: 150px;
  overflow: hidden;
  position: relative;
  width: 150px;
Enter fullscreen mode Exit fullscreen mode

Height and width obviously only need to match each other, but could be anything.

Then you give the inner elements CSS to make them all appear initially as first-quarter quadrants of the pie container.

button {
  bottom: 50%;
  height: 100%;
  left: 50%;
  position: absolute;
  transform-origin: bottom left;
  width: 100%;
Enter fullscreen mode Exit fullscreen mode

What you have so far would look something like this:


The transform-origin probably seems out of place there, but it makes sense later, and is the key to the whole thing...

The final key to making the wedges is the calculation for transformation of each square into a wedge. This is done by using an ordered combination of:

  • transform: rotate()
  • transform: skeyY()

To make the calculations we need to know the number of degrees each wedge should take in the circle. Let's say
share = 120
which is correct for our example with 3 equal shares, and we need an iterator, let's use
i = 0
(I'm sure you can see how this will translate into a dynamic JS function for any number of wedges...)

Now the calculation is as follows per wedge in order of appearance:

rotate = (i * share)deg
skeyY = (share - 90)deg

Minus 90 because the wedge starts out square

Basically the rotation turns the wedge on it's bottom left corner (which is the center of the pie) the number of degrees of all the wedges that are before it. The skewY skews the wedge from being a rectangle to being a wedge of the right degrees.

Then we have to counter the skewY and rotation on the inner element (especially if you want text there), the calculation for that is:

rotate = (share / 2)deg
skewY(-(share - 90)deg

This will reverse the transformation and rotate the text to appear 45 degrees relative to it's containing 'wedge'.

Now your markup will look like this:

<div class="pie">
  <button style="transform: rotate(0deg) skewY(30deg)">
    <span style="transform: skewY(-30deg) rotate(60deg)" class="text">1</span>    
  <button style="transform: rotate(120deg) skewY(30deg)">
    <span style="transform: skewY(-30deg) rotate(60deg)" class="text">2</span>
  <button style="transform: rotate(240deg) skewY(30deg)">
    <span style="transform: skewY(-30deg) rotate(60deg)"class="text">3</span>
Enter fullscreen mode Exit fullscreen mode

Fair warning
The order or the transform properties is important. Try switching the order and it won't work. I don't have enough time to figure that out, so if anyone here wants to explain that - go for it!

Here's how it looks in the end, with a bit of extra css so you can see the outcome better.

And if you're interested in making it dynamic, here's a very simple implementation:

Caveats and thoughts...

Don't try this on Internet Exploder - I haven't, and I won't...

The only real caveat is that you are limited to a minimum of three wedges. The maximum is really dependent on the inner content of the wedges as well as the overall size of the 'pie'...

If you're interested, this could most likely be quite easily adapted into a simple pie chart engine - but I don't have the time to figure out the details.

Hope someone in need finds this, and it helps :-)

Top comments (1)

myleftshoe profile image

"Internet Exploder" - funny!

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await