The curious case of border-radius:50%

Admittedly, percentages in border-radius are not one of the most common use cases. Some even consider them an edge case, since most people seem to set border-radius in pixels or –rarely– ems. And since it’s not used very frequently, it’s still quite buggy. A bit of a chicken and egg case actually: Is it buggy because it’s used rarely or is it used rarely because it’s buggy? My vote would go to the first, so the purpose of this post is to let people know about why percentages in border-radius are incredibly useful and to highlight the various browser whims when it comes to rendering them.

Specification

Before we go into discussing implementations, let’s first examine what the right thing to do is, i.e. what the specification says:

Percentages: Refer to corresponding dimension of the border box.

The two length or percentage values of the ‘border-*-radius’ properties define the radii of a quarter ellipse that defines the shape of the corner of the outer border edge (see the diagram below). The first value is the horizontal radius, the second the vertical radius. If the second value is omitted it is copied from the first. If either length is zero, the corner is square, not rounded. Percentages for the horizontal radius refer to the width of the border box, whereas percentages for the vertical radius refer to the height of the border box.

Why is that useful?

It’s the only way of utilizing border-radius to draw a circle or ellipse, i.e. a rounded shape without any straight lines whatsoever (without knowing the dimensions in advance).

As you will see below, Firefox used to have a bug, or actually a different interpretation of the spec, which I think is a quite commonly needed use case, even more than ellipses: It always drew a regular curve for the corners (quarter of a circle) with the maximum possible radii. This is a very commonly needed shape in UI design. If you’re using OSX, you’re seeing it everywhere: the buttons, the scrollbars, even Skype (notice the blue or grey shading around the usernames in a chat). As I’m writing this post, I can see the same shape in the buttons of WordPress’ admin panel. And as the current spec stands, there’s no way to do that. You have to know the height (or width, if you want a vertical shape) in advance, which even when possible, makes border-radius depend on the value of other attributes (such as line-height) and you have to remember to change it every time you change those, which causes maintenance headaches. And what’s worse is that the Backgrounds & Borders module is almost done, so it’s quite unlikely that this will change anytime soon. :(

As noted in this comment by David Baron, that assumption wasn’t exactly correct about Firefox’s old rendering. It just resolved % as relative to width in every case (kinda like percentages in margins) and when the height was smaller than the width, it applied the rules for radii that are too big, which say to reduce it equally. A straightforward deduction is that we do have a standards-compliant way to get the behavior from old versions of Firefox, in every browser: Just specify a very big radius, like 9999px.

Different implementations, different bugs

Rendering of the testcases in Firefox 4 beta 6

Firefox 4 beta 6

As I mentioned above, Gecko up to Firefox version 4 beta 6 always draws a regular curve for the corners with the largest radii applicable, resulting in a shape that is either a perfect circle or a rectangle with a semicircle on top and bottom (if height > width) or right and left (if width > height).

Rendering of the testcases in Minefield (latest Gecko nightlies)

Minefield (latest Gecko nightlies)

In the latest nightlies this bug is fixed, and it follows the spec to the letter. I can’t help but wonder if this was a bug, a misinterpretation of the spec or a deliberate disagreement with it.

Rendering of the testcase in the latest Webkit nightlies

Webkit

Webkit was late to support percentages in border-radius, but it seems to be the first (it or IE9, I’m not sure) to follow the spec to the letter –concerning corner radii at least– and renders an ellipse (horizontal radius = width/2, vertical radius = height/2) no matter what. Webkit however seems to be having serious trouble with borders, rendering them with variable width strokes (!).

Rendering of the testcases in Opera

Opera 11

Presto (Opera) is the weirdest when it comes to rendering a percentage border-radius. I can’t figure out the algorithm it uses to determine the radii of the corners even if it was to save my life, it even changes according to window size in my testcases! Since I’ve been using border-radius:50% regularly, I’ve had the pleasure of observing Opera’s rendering in many different designs and I still can’t find a pattern. It’s particularly funny when rendering the little fuchsia comment bubbles in the homepage of my blog: Every one of them has a different radius, even if they are about the same size. It even got one of them right and rendered it as an ellipse once!

Rendering of the testcases in IE9

Internet Explorer 9

Trident (IE9), along with the latest Gecko nightly is the only 100% correct one when it comes to rendering the testcases, which is not surprising since the IE team boasted quite a lot for their bulletproof border-radius implementation. Well, their CSS3 support might be a bit lacking, but at least the bits they actually implement aren’t buggy. Kudos for that.



Link to testcases

Note: Of course all bugs mentioned above have been reported to the respective browser vendors (except the Gecko one that is already fixed in the nightlies).

  • http://Drawingthemaximumradii kyb

    So, it looks like the choice firefox is making is whether the 50% is the same for both x and y of the object being styled.

    It’d be nice to be able to be able to specify to choose just one- i.e. border-radius: height(50%) or border-radius: width(50%), rather than using the height for one side and the width for the other.

    • http://leaverou.me Lea Verou

      Exactly.

      It would be nice to be able to specify that. I also think I’d be nice to be able to style properties depending on the value of other properties in general (via a current() function for example), so we’d be able to do things like border-radius: calc(0.5 * current(‘width’));
      But, I’m sure there’s a pretty good reason that something like that isn’t considered for CSS.

  • http://dbaron.org/ David Baron

    You can get the behavior from old versions of Firefox by specifying border-radius:10000px or some other similarly large value. (It works the same way, too. Firefox’s old rule was that % was relative to width, and then it applied the rules for radii that are too big, which say to reduce the radii equally.)

    • http://leaverou.me Lea Verou

      Wow, honored to have such a W3C CSS WG superstar here :)

      Excellent points too. Post edited.

  • http://sideradesign.com paul

    I made an experiment which uses multiple backgrounds, border radius percentages , background-clip and background-origin and the results are here: http://dl.dropbox.com/u/426954/CSS3%20backgrounds/buttons.html

    the thing that bothers me is that webkit renders the padding-box as rectangular instead of following the curve of the border

    • http://leaverou.me Lea Verou

      I get a 404 when I try to access that file.

  • http://twitter.com/necolas Nicolas Gallagher

    Interesting round-up of the issues. Hadn’t noticed that Firefox < 4 bug before. I came across Opera 10.5's bizarre border-radius implementation while I was working on this experiment (skip down to "Modern browser CSS3 inconsistencies") – http://nicolasgallagher.com/css-pseudo-element-solar-system/. I didn't really know what it was doing either. Shame that it is still not fixed in Opera 11. Hopefully soon.

    • http://leaverou.me Lea Verou

      Nicolas*, you didn’t need to set the border-radii in different pixel measurements per element if all you wanted was circles. Just set it to a very large value (I usually use 9999px, to be able to easily spot it in my stylesheets)

      *I’m confused, is your first name Necolas or Nicolas? :)

      • http://twitter.com/necolas Nicolas Gallagher

        Yes that’s true, but using % ended up showing me Opera’s bug.

        My name is Nicolas! I just used ‘necolas’ for usernames because ‘nicolas’ is always taken.

  • Cyrylski

     There is a very curious issue that I discovered when fiddling around with the rounded corners. When a border is very thick, let’s say around couple thounsand pixels, roundness turns into a flower-like shape in Chrome! Taking your test cases into account, if I add this CSS to .round class:
    width: 60em;
    height: 60em;
    border: 100em red solid;

    this effect can be observed. Does not occur in Firefox though…

    • Cyrylski

       Also, I provided a screenshot collection to illustrate the problem:

      http://img690.imageshack.us/img690/8044/screenshotsxx.png

    • http://leaverou.me Lea Verou

      Browsers do all kinds of weird stuff on edge cases like this. I’ve observed rendering problems with border-radius & very thick borders in Gecko, Presto and Webkit. 
      You should report this as a bug to http://bugs.webkit.org  

      • socialblogsite

        I think “WTF” summarizes it all.

  • Pingback: Fronteers conference 2011 Day 1 – my thoughts « RND Websites

  • socialblogsite

    You STILL have to be careful not to specify a corner radius bigger than the available area, and when I say “available” it means for many reasons:
    You could be asking for 50% of the width, but in box-sizing: border-box mode, after you substract the room taken by let’s say 1px border, you DON’T have 50% of the width available for your curve anymore, but 50% – 1/2 px.

    The browsers have to average/fix that and Safari still shows an error IN THE SHADOW casted.

    In 2012 I wrote about the bug in Safari – still unresolved in my version v 5.1.9 – showing the curve fine, but the drop-shadow square.

    http://socialblogsitewebdesign.com/watch-out-safaris-border-radius-specific-values-breaking-box-shadow/

    Here’s a working demo: http://jsfiddle.net/jzpV6/2/