Style Twitter posts on your site with CSS3

This article first appeared in issue 215 of .net magazine - the world's best-selling magazine for web designers and developers.

The popularity of Twitter has made a list of recent tweets a standard component of many websites. At its core, such a record is simply an ol element, and, apart from a cute bird icon, is often styled to look just like any other list. But we can use the new properties and selectors of CSS3 to make it look a little more interesting in modern browsers.

Here's a tweet list with basic styles applied; no CSS3, just standard font, background and colour styling.

Vanilla tweet list, with no effects added

Vanilla tweet list, with no effects added

It’s usable and clean. Anyone seeing it in an older browser wouldn’t think it was broken or incomplete. But there’s nothing about the visual style that says this is a list of tweets, as opposed to a list of news articles, documents or groceries.

Let’s make two changes. We’ll style the heading to look like a speech bubble, conveying the idea that the tweets are someone’s words. And we’ll convey the idea of progression by making the most recent posts more prominent. Download the tutorial files from above and open tweets_start.css in your text editor.

The first step towards making the heading look like a speech bubble is to give it rounded corners. This is easily and efficiently accomplished with the CSS3 border-radius property. Add it to the existing h2 rule:

h2 { /* Existing CSS properties omitted for brevity. */ border-radius: 20px;}

This single line will make the corners rounded in Chrome, Firefox 4, IE9, Opera and Safari 5. Firefox 3.6 or earlier and Safari 4 or earlier need the -moz-border-radius and -webkit-border-radius properties respectively. Browser vendors use these browser-specific prefixes when the specification is still being worked out and they think it may change. Add the prefixed versions before the plain border-radius line, so that as soon as the non-prefixed property is supported by a given browser, it will override the earlier declarations, which may use non-standard behaviour from an older version of the spec.

h2 { /* Existing CSS properties omitted for brevity. */ -moz-border-radius: 20px; -webkit-border-radius: 20px; border-radius: 20px; }

Now the heading has rounded corners in all modern browsers. We also need to round the top-right corner of the light green tweet list to match, since it’s now sticking out from behind the rounded heading. Add these border-radius declarations to the #tweets rule:

#tweets { /* Existing CSS properties omitted for brevity. */ -moz-border-radius: 0 20px 0 0; -webkit-border-top-right-radius: 20px; border-radius: 0 20px 0 0; }

With all the corner-rounding taken care of, it’s now time to complete the heading’s speech bubble appearance by adding a tail (the little arrow pointing down). We can add that without using an image; just simple borders, generated content and absolute positioning from CSS2.

Before creating the triangular tail, you need to understand that when two borders of a box meet at a corner, the browser draws their meeting point at an angle. If you reduce that box’s width and height to zero, and give every border a thick width and different colour, you’ll end up with the appearance of four triangles, each pointing in a different direction. If you made the right, left and bottom borders transparent instead, only the top border would be visible, leaving the appearance of a downward-pointing triangle.

Adjoining borders of a box meet at an angle

Adjoining borders of a box meet at an angle

The thick borders form the appearance of four pushed-together triangles


When the box has no width or height, the thick borders form the appearance of four pushed-together triangles

These thick borders are all we need to create the triangle shape of the tail, but we require an element on which to place the borders. We could stick an empty span or div after the heading, but it’s best to leave the HTML pristine and use CSS-generated content to create an empty element out of thin air. Generated content is nothing new to CSS3, and while it shouldn’t be used for adding essential content to the page, it’s perfect for adding decorative details such as our little tail.

To make generated content, you first need to specify where the content is to be inserted, using either the ::before or ::after pseudo-elements, also written as :before and :after for better backwards-compatibility. Create a new rule with h2:after as the selector. In this rule, we specify what content to insert using the content property. Here, we just want an empty element we can apply borders to, so add the content property with a null value (empty quotation marks) to the h2:after rule. Then give it 10px-wide borders, and set its width and height to zero, so that each border takes the shape of a triangle:

h2:after { content: ""; width: 0; height: 0; border-width: 10px; border-style: solid; border-color: #048753 #048753 transparent transparent; }

Using the same colour for the top and right borders, plus transparent borders on the bottom and left, creates the appearance of a downward-pointing triangle with a straight right edge.

This triangle is currently placed right after the h2’s content, but we can move it wherever we like with absolute positioning. We already have position: relative; in the h2 rule in order to fix IE6’s bug with negative margins, so the heading is established as the reference point for a child absolute element’s positioning. Go ahead and add the absolute positioning to the generated content, along with bottom and left values:

h2:after { content: ""; position: absolute; bottom: -20px; left: 30px; width: 0; height: 0; border-width: 10px; border-style: solid; border-color: #048753 #048753 transparent transparent; }

I’ve set the left value to 30px, making it greater than the 20px border-radius value to ensure it lands on the straight edge of the heading, to the right of the bottom-left corner curve. The bottom value is negative in order to pull the triangle downwards out of the heading, and it matches the height of the triangle so that it sits flush with the lower edge of the heading. The tail now appears to be seamlessly connected to the rounded heading above it, forming one cohesive speech bubble.

The heading now looks like a speech bubble

The heading now looks like a speech bubble, but we didn't have to use a single image, making it more efficient to develop and maintain as well as faster for the user to download

Showing progression

Let’s now work on creating an appearance of progression within the list of tweets. The tweets aren’t in a random order, but are arranged with the most recent (and arguably most important) first. It would be good if the visual styles could convey this sense of flow as well. We can use the new :nth-child() pseudo-class to select certain tweets based on where they are in the list, and then apply simple styles that make the most recent posts more prominent and the older ones less so, as though they’re fading away in time.

Pseudo-classes and pseudo-elements can be used to select specific elements in the HTML without assigning ids or classes, keeping your markup cleaner. CSS3 added several new structural pseudo-classes that enable us to precisely target elements in the document tree based on unique characteristics, such as relative placement.

For instance, the :last-child pseudo-class targets an element that’s the final child element of its parent element. It’s this characteristic of being last that we can now select by, without having to add a class or id.

One of the most powerful and useful new structural pseudo-classes is :nth-child(). It selects an element based on its position within the list of its parent’s children. In other words, it selects an element based on how many siblings it has before it.

You write the position number of the element you want to select inside the parentheses of the selector, such as li:nth-child(5). This selector would match the fifth li element in a list.

In addition to numbers inside the parentheses (the selector’s argument), you can also use the keyword odd or even to select every other element in a row. But where :nth-child() gets really powerful is when you use a formula as its argument, enabling you to create more complex alternating patterns or select specific blocks of sequential children at a time.

The formula has the syntax an+b, where a is a cycle size that you pick, n is a counter starting at zero and b is an offset value that you pick. An example of this formula is li:nth-child(3n+1). Since n starts at zero and then increases by one each cycle, this selector would match:

(3 x 0) + 1 = 1 = 1st list item
(3 x 1) + 1 = 4 = 4th list item
(3 x 2) + 1 = 7 = 7th list item

And so on! While you could certainly add classes to the first, fourth and seventh list items, using the :nth-child() pseudo-class keeps track of position numbers for you and matches accordingly. It’s far more efficient to develop, easier to maintain and mistake-proof.

In the :nth-child() formula, if the value of a is 1, you can omit it, so 1n+5 is the same as n+5.
Since you’re only incrementing by 1 each time, a formula like this will select a sequential block of children, rather than skipping over certain ones.

Check it out:

(1 x 0) + 5 = 5 = 5th child
(1 x 1) + 5 = 6 = 6th child
(1 x 2) + 5 = 7 = 7th child

On it goes, until there are no more list items to select. The selector li:nth-child(n+5) thus picks all the list items from the fifth onward.

What if, instead, you wanted to target the first five? Simple: just make the value of a negative instead of positive. The selector :nth-child(-n+5) matches:

(-1 x 0) + 5 = 5 = 5th list item
(-1 x 1) + 5 = 4 = 4th list item
(-1 x 2) + 5 = 3 = 3rd list item
(-1 x 3) + 5 = 2 = 2nd list item
(-1 x 4) + 5 = 1 = 1st list item
(-1 x 5) + 5 = 0 = no list items

What does all this maths have to do with our tweet list? Well, if we want to style a sequence of posts at the top differently to those at the bottom, we just need to use :nth-child() with a cycle size of 1 or -1 (plain n or -n), and an offset value equal to the position in the list we want to start selecting up or down from.

To figure out the correct :nth-child() formulas, it’s helpful to visualise the unique styles we want and where we want them to appear along the list. Let’s say we want to make the first tweet really big and dark, the following two slightly smaller and lighter, the next two even smaller and lighter, and all the rest extremely small and light.

Targeting the first tweet and the sequence of last tweets is easy with :nth-child(). But how do we target just the second and third together, and just the fourth and fifth together?

Technically, we can’t. But we can focus on the top five, then override those styles on the top three, finally overriding both styles on the top one. Let’s start at the bottom and work our way up. We want to make the three oldest tweets very small and light. One way to do this would be to apply the small font size and lighter grey colour to the global li rule:

li { margin: 0 0 1em 0; font-size: 88%; color: #666; }

We could then override these styles as necessary with subsequent :nth-child() rules for the top five, top three and top one, leaving only the sixth and later tweets with these styles applied.

The problem with this approach is that browsers that don’t understand :nth-child(), such as IE8 and earlier, won’t be able to apply the overriding rules for more recent tweets. Users of these browsers will therefore see all the tweets with the small font and light colour.

This isn’t wrong, but in this case, if someone can’t see the bold styling for recent tweets, I’d prefer that they didn’t see the muted styling for older posts either. Instead, I’d like IE users to see something intermediate for everything in the list. To make this possible, apply the font size and colour declarations to a new rule that still selects all the tweets but uses :nth-child() in the selector:

li:nth-child(n) { font-size: 88%; color: #666; }

A value of n has a cycle of 1 and no offset, so it always selects all the children. This selector chooses exactly the same thing as the simple selector of li. But by including the :nth-child() syntax, we’ve targeted all the li elements in such a way that IE8 and earlier can’t understand, making it ignore the rule. IE8 and earlier get the standard font size and black text, while all other modern browsers see the smaller font size and grey text.

All the Tweets now have small, gray text

In browsers other than IE8 and earlier, all the Tweets now have small, gray text. We'll override those styles on the more recent Tweets with multiple :nth-child() rules

Now that the oldest tweets are styled, let’s start moving up the list, applying more prominent styles as we go. First, we’ll work on the top five tweets using the selector li:nth-child(-n+5). Make sure to add it beneath the first :nth-child() rule so it takes precedence:

li:nth-child(-n+5) { font-size: 110%; color: #4F4F4F; }

This overrides the li:nth-child(n) rule on the top five items in the list only, making them a little larger and darker.

The top five Tweets now have a unique style from the rest

The li:nth-child(-n+5) rule selects all children that are the fifth and earlier, giving the top five Tweets a unique style from the rest

What we want, though, is for this appearance to only apply to the fourth and fifth tweets, not all of the top five. To do that, we need to add another rule for the third, second and first items:

li:nth-child(-n+3) { font-size: 125%; color: #333; }

This overrides both of the earlier styles on only the top three tweets, creating the appearance that we’ve selected the fourth and fifth posts independently.

The li:nth-child(-n+3) rule overlaps the li:nth-child(-n+5) rule on the top three Tweets

The li:nth-child(-n+3) rule overlaps the li:nth-child(-n+5) rule on the top three Tweets, leaving a unique style on only the fourth and fifth Tweets, even though we haven't actually targeted them separately

Finally, we need to create an overriding style for the first tweet in the list. There are a number of selectors we could use: li:nth-child(-n+1), li:nth-child(1) or li:first-child. To keep with our :nth-child() theme, let’s try li:nth-child(1):

li:nth-child(1) { font-size: 150%; color: #000; }

Now the first and most recent tweet has the most prominent styling, with pure black text and a large font size. The overall list visually conveys the idea of a progression, with the oldest items fading away and becoming more muted.

Tweets are now styled based on their position in the list

The Tweets are now styled based on their position in the list, with the most recent and important having the most prominent appearance

Because we’ve used :nth-child() instead of classes, it’s easy to adjust the number of tweets in the list without messing up the styling, or change which tweets have a certain style without having to touch the HTML.

You can now put the :nth-child() selector to use on your sites to create a wide range of alternating patterns or style select sequences of elements in a flexible, efficient way.

Older browsers

Earlier versions of IE don’t support most of the effects we’ve added in this tutorial. Since they’re purely visual enhancements, unrelated to usability or functionality, I think this is an acceptable use of CSS3. But if you must provide solutions for IE to make sure it emulates the other browsers, you do have a few options.

To create rounded corners without border-radius, use images of each corner, placed as background images on nested divs, or use a script that creates the rounded corners for you. Either way, hide it from browsers that don’t need it using a tool such as Modernizr.

The speech bubble’s tail shows up fine in IE8, but IE7 and earlier don’t support generated content, so they don’t see it. If you’re providing a rounded corner solution for IE and want to complete the speech bubble appearance with a tail, you’ll need to add another element to the HTML manually, such as an empty span, and turn this into the triangle using the same border, box and positioning properties we used on the generated content.

Finally, the :nth-child() selector that creates the progressively smaller and fainter posts isn’t natively supported in IE8 and earlier, but support can be added through scripting. Two ready-to-use scripts that make many CSS3 selectors work are Selectivizr and IE7.js.

Thank you for reading 5 articles this month* Join now for unlimited access

Enjoy your first month for just £1 / $1 / €1

*Read 5 free articles per month without a subscription

Join now for unlimited access

Try first month for just £1 / $1 / €1

The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Deals Editor Beren Neale, Senior News Editor Daniel Piper, Digital Arts and Design Editor Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.