Fluid frame using :before and :after

What is it and why is it important? In an attempt to get away from bad markup and to completely separate presentation from structure, I set myself the task of creating a visual framing effect using just a single element. All eight parts of the element must be styled; the four corners, and the four sides. It has been done before, usually with tables or nested elements, or even sometimes using a single element of fixed dimensions. However, inspired by the works of the great Moose, I decided to make things harder for myself by saying that it must have completely fluid dimensions, so that it could get wider and higher without breaking the effect. Using nothing but CSS 2 (2.1 to be precise). Not an easy task.

At first, I started with the basic idea of applying rounded corners to an element (I chose to use a paragraph, but basically, anything could work). But no, this seemed too simple. How about a full framing effect, where all four sides, and the corners were styled using images. The idea was to make the text appear to be floating on some translucent glass, with a curved holder effect on both the top and bottom. For it to be translucent, the shadow it created would have to be visible through the central area, and at all points where the shadow was offset. This was starting to seem impossible, but I gave it a go anyway.

Building the effect

After creating my image of what I wanted it to look like, I made the shadow image for the right hand side, and I applied this to the right side of the image, repeating vertically. This repeat allows the paragraph to change size, but still work. And every current browser happily played along. I also made the paragraph position:relative; with no offset. Still no problems.

I also applied a background color. Still no problems. Images are not transparent, as transparency will only show the background of the paragraph.

Now I needed to apply a top-right image. To do this, I added a top margin to prevent it obscuring the words, then I used p:before to create the pseudo element. At this point I lost IE 7-, because its archaic CSS support includes virtually none of the CSS 2 specification. Because the paragraph is relatively positioned, I could use absolute positioning here to position it in the top-left corner. At this point, I lost Mozilla/FireFox 3.0- and ICE (don't worry, I will force Mozilla to behave later). They support the generated content, but follow the mistake in the CSS 2.0 specification that says that it cannot be styled as a positioned/floated element. Although this mistake was corrected in the CSS 2.1 revision, the ICE engine has still not managed to catch up, and the Mozilla/FireFox engine finally managed to catch up as late as Firefox 3.1, long after I first wrote this article. Not to be put off by browsers whose behaviour is incorrect, I set the width to 100% (since I am talking about positioned elements, this means 100% of the parent container's border-box-width). I set the height to the height of the top-right image. Again, I added padding to the paragraph to prevent this from obscuring the text.

The next step was to actually add the top-right image. The image is applied to the background of the :before pseudo element, and is right aligned. This way, the corner appears in the right place, and the image is automatically cut off where it reaches the left edge. The image provides the rounded top-right corner, as well as the top effect. It produces the first limitation. The maximum width of the paragraph is limited to the width of this image. So I also used max-width to ensure that on exceptionally wide displays, the effect did not break.

Now to add the top-left image. This was simply inserted using content:url(); in p:before. The image overlaps the background, hiding the fact that the background actually continues underneath it (ICE just writes the image address). But here I now had a problem. There was no way to attach an image to the left side of the paragraph. In fact, both Moose and I struggled with this for a while. Moose tried adding the image into one of the :before or :after pseudo elements, and clip it to the desired 100% height. But then he realised that percentage clipping applies to the :before and not the paragraph. We could make the :before 100% of the paragraph height, but then it would obscure the contents from clicks etc, making it impossible to use links, and this was not acceptable.

After playing with the idea for two days, I hit on the answer. Even though the height of the :before pseudo element was fixed, contents are allowed to overflow their containers. So I made the top-left image very tall, so that it could also provide the left effect. Some left padding ensures that the image does not overlay the content. It produces the second limitation. The maximum height of the paragraph is limited to the height of this image. Setting overflow:hidden; on the paragraph prevents the tall image from extending below it. This was the result that Moose and I had been looking for. Note, max-height should not be used as this could obscure content.

Now all that remained was to add two images on to the bottom the same way as originally done at the top. :after is positioned at the bottom (at which point Konqueror 3.2 and iCab 3.0.0 fail, because they treat bottom as top when dealing with generated content), is given a background image that produces the bottom-right corner and bottom effect, and is given a bottom-left corner using content: Opera 7.5+, Safari, Konqueror 3.3 and iCab 3.0.3 have made it this far. Opera 8 will not allow the text in the paragraphs to be selected, but it does allow links to be clicked, and the bug is fixed in Opera 9, so I am not too unhappy. It will also make the scrollbar long enough for the image, even though it is not needed, but this is harmless. IE 7-, ICE and Konqueror 3.2 do not produce the correct effect, but at least it is perfectly readable and accessible, and in fact, it doesn't look too bad at all. Only Mozilla/Firefox 3.0- makes a mess, because it puts the :before inside the main content as an inline box instead of correctly as a block. As a result, the tall image is inserted before the text in the paragraph, pushing the text far down the page.

You want it to work in Mozilla/Firefox 3.0- as well? Well, it is possible, but not so pretty. By setting display:block; on the generated content instead, Mozilla will play along without having any adverse effect on the other browsers. However, as you can see with the way it renders this paragraph, it will leave the padding in place, outside the frame. It looks ugly, but not as bad as the way it renders without it. By removing the positioning, the effect works properly. Adding some left and right padding stops the contents spilling over the edges and spoiling the shadow effect, but this must be counteracted with negative margins on the :before and :after pseudo elements to make sure they reach the edges of the paragraph.

The benefit of this Mozilla friendly version is that it avoids the minor problems in Opera 7.5-8 as well. This works well enough that I use it on the main part of this site in the 'Blue Frame' style, giving a working effect in Opera, Safari, Konqueror, iCab and yes, even Firefox and IE 8, while degrading nicely in IE 7-. Note, however, that Mozilla 1.8 (and therefore Firefox 1) still screws it up. It insists on extending the paragraph below the :after pseudo element (caused by the left and right paddings, and the overflow: hidden; - go figure), spoiling the effect - this seems to be unavoidable, but thankfully it is fixed in Firefox 1.5. It's your choice. Do with it whatever you decide.

Those of you using Opera 9.5+, Safari/OmniWeb, Konqueror 3.4+, Firefox 3.1+ or iCab 3.0.3+ might also notice I have used text-shadow to enhance the translucent effect, making it appear that the words are also casting a shadow on the surface below the "glass". When other browsers implement this declaration, it will work in them too. This is not part of the fluid frame effect though, I just added it because I like it.

Back to main article