Rounded Corners

Ugh. If there is one task that will kill my motivation for the day, it’s rounding boxes with CSS. Yes, in the right context they do give a design an edge, but that doesn’t mean that they aren’t a real pain to do! The subject has really been done to death by now- there are numerous technique round-ups here, here and probably many more. One thing I noticed though, is that most of these tutorials and examples concentrate on producing one style of box. If I wanted to change the background-color of the box for example, or the corner radius, I’d have to fire-up Photoshop and then create a new set of rules. Following on from my forays into Sass last week, there must surely be a way of consolidating the styles into a fire and (sort of) forget solution. The CSS markup in this exercise will be written in Sass. It is by no means necessary, but the auto nesting feature saves a lot of time and you may find that this exercise is more work than it is worth without it! CSS3 should put all these shenanigans to rest, but it is unfortunately still not mainstream enough <cough>IE</cough> to be used on production environment.

The first thing that I did was to create the sprite that will act as the round corners. I am creating 4 radius variations for each corner in this sprite: 5px, 9px, 14px and 18px. You can create as many as you feel you will need. I am doing this by drawing transparent circles of the desired radius on a layer with a colour the same as the background. The transparency will let us use CSS to change the colour of the box and we will use CSS background positioning to find the right part of the image later.

We will need to create a new sprite for any variant in background color unfortunately, I am unable to negate this step fully. However, it is quite common for a site to have a single fixed background color so normaly you would only need the 1 sprite for a site. In using transparency also, IE6 won’t be able to use these images properly without some workarounds, but I think it’s an acceptable trade-off for the flexability that we gain. The image I end up with is this…

rnd box white1 Rounded Corners

The next step is to determine the markup for our round box. The priority for this experiment is flexibility rather than semantic accuracy. That is not to say that semantic markup is a non-issue however, just that I would rather have an extra div here and there over a box that is difficult to adapt. With that said, the markup I settled on is this

<div id="whatever: class"rnd-box r[x]">
  <div class="corners white">
    <div class="tl"></div>
    <div class="tr"></div>
    <div class="br"></div>
    <div class="bl"></div>
  </div>
  <div class="box-contents">
    Content of Box goes here
  </div>
</div>

We have a div for our box, with class ‘rnd-box’ and r[x], where [x] is the radius of our desired corners (for example, i would use r9 for 9px radius corners). Inside this we have two divs, one with class ‘corners’ which contains a further 4 divs that represent our 4 corners (top-left, top-right, bottom-right, bottom-left) and the other div with class ‘box-contents’. This second div isn’t strictly necessary, but will make styling a bit easier later, should we want a bit of margin between the content and box edges. Definitely not the cleanest of markups, but not the worst I’ve seen either.

With that set we can start the styling process. The most important part is to set

.rnd-box
  position: relative

Without this, we wouldn’t be able to position our corners correctly. The next thing is to set the positions of our corners, using absolute positioning

  .corners
    .tl,
    .tr,
    .br,
    .bl
      position: absolute
      background:
        attachment: scroll
        color: transparent
        repeat: no-repeat

    .tl
      top: 0
      left: 0

    .tr
      top: 0
      right: 0

    .br
      bottom: 0
      right: 0

    .bl
      bottom: 0
      left: 0

Absolutely positioned elements take their positions relative to the nearest ancestor element with relative positioning, which in this case is the div .rnd-box. It is this relative/absolute relationship that allows the corners to be positioned where we want them with minimal fuss. I have set some background properties here in advance, as they won’t be changed at all.

We now set the background image for each of the corners, parenting them under a class name that corresponds to the background color, and assigning the sprite we created (here located at /images/rnd-boxes/rnd-box-white.png) to each of the corners as a background image..

    .white
      .tl,
      .tr,
      .br,
      .bl
        background-image: url(/images/round-boxes/rnd-box-white.png)

This section of code should be all you need to add to later, if you wanted a box on a different background color. For example, if I made another sprite with a gray background at /images/round-boxes/rnd-box-gray.png, I would add the snippet

    .gray
      .tl,
      .tr,
      .br,
      .bl
        background-image: url(/images/round-boxes/rnd-box-gray.png)

and change the markup to use <div class="corners gray r[x]"></div>.

The final part is the most tedious, but should only need to be done once. For each radius variant (r5, r9, r14, r18) we need a set of rules that define the size of the corner and the position of the sprite of that corner. The Sass for the 9px radius corners for example would be

  .r9
    .tl,
    .tr,
    .br,
    .bl
      width: 9px
      height: 9px
    .tl
      background-position: -11px -9px
    .tr
      background-position: -20px -9px
    .br
      background-position: -20px -18px
    .bl
      background-position: -11px -18px

Repeat this for all your variants, and combined with the steps previous and you should have something like this…

.rnd-box
  position: relative

  .corners
    .tl,
    .tr,
    .br,
    .bl
      position: absolute
      background:
        attachment: scroll
        color: transparent
        repeat: no-repeat

    .tl
      top: 0
      left: 0

    .tr
      top: 0
      right: 0

    .br
      bottom: 0
      right: 0

    .bl
      bottom: 0
      left: 0

  .white
    .tl,
    .tr,
    .br,
    .bl
      background-image: url(/images/round-boxes/rnd-box-white.png)

  .r5
    .tl,
    .tr,
    .br,
    .bl
      width: 5px
      height: 5px
    .tl
      background-position: 0px -13px
    .tr
      background-position: -5px -13px
    .br
      background-position: -5px -18px
    .bl
      background-position: 0px -18px

  .r9
    .tl,
    .tr,
    .br,
    .bl
      width: 9px
      height: 9px
    .tl
      background-position: -11px -9px
    .tr
      background-position: -20px -9px
    .br
      background-position: -20px -18px
    .bl
      background-position: -11px -18px

  .r14
    .tl,
    .tr,
    .br,
    .bl
      width: 14px
      height: 14px
    .tl
      background-position: -30px -4px
    .tr
      background-position: -44px -4px
    .br
      background-position: -44px -18px
    .bl
      background-position: -30px -18px

  .r18
    .tl,
    .tr,
    .br,
    .bl
      width: 18px
      height: 18px
    .tl
      background-position: -59px -0px
    .tr
      background-position: -77px -0px
    .br
      background-position: -77px -18px
    .bl
      background-position: -59px -18px

As an added bonus, because these corners are independent of and sit above the box itself, not only can we change the box colour with just CSS as I mentioned earlier, but we can add background images to the box also. See the example.

3 Comments

RSS feed for comments on this post. TrackBack URL

  1. Francisco — November 27, 2009

    Excellent solution, I really like it.
    Congratulations, you really make it look easy and clean. Professional.
    Keep up the good work.

  2. peter — March 18, 2010

    Hey Simon,

    quite nice approach. How would you handle the position of tl,tr,bl,br for a div with a border?

    BTW: In your last code-sample is a missing blank for .tl top: 0, which results in a mal positioning of that thing.

    Thanks for sharing this.

    Peter

  3. Simon Tsang — March 26, 2010

    Hi Peter,

    Thanks for the spot- I’ve updated the snippets now.

    As for a div with a border, this technique wouldn’t really be suitable I’m afraid :(

    Off the top of my head, you might be able to get away with using a negative value equal to the border-width instead of 0. For this to work though, you would have to draw a ring of colour inside each circle, with a thickness and colour equal to the border colour and thickness. So for a div with border of 3px, the tl would have position of top:-3px; left:-3px. I’ll explore this when I have a spare moment or two!

Leave a comment

Preview: