Simple Talk is now part of the Redgate Community hub - find out why

Making HTML tables easier on the eye- CSS Structural Pseudo-classes

We asked Phil why his PowerShell tabular reports looked so nice. 'CSS structural pseudo-classes' he muttered mystically. Later on, without any further warning, he popped up with this article that explains for anyone who has missed them, how to go about doing intricate formatting of an HTML file, the contents of which you cannot alter.

It is a common predicament: You have an HTML fragment with a table, list or dictionary  in it, generated from some data, and you have to render it in such a way that the data is easy to read, and the information presented in such a way as to prevent misunderstandings: However, you can’t alter the HTML source in order to add CSS classes to individual elements. We’ll take a couple of practical examples to show how you can solve this sort of problem.

The flurry of browser upgrades has had the result that a lot of useful CSS2 and CSS3 has been recently implemented. As well as helping the creation of the more esoteric mobile-applications and games, it is the extended ways of addressing individual DOM elements that is making the basic layout of text and tables a lot easier. This article aims to illustrate practical ways of doing this

With CSS3, you can more easily apply styles to elements of the DOM based on their location within the DOM. This solves many problems with the basic layout of text.  You may be faced with the requirement that the first paragraph in a DIV section may be formatted differently, you may want to tuck a bulleted list in under the paragraph that precedes it.  You may want code to have minimum paragraph spacing when it is preceded by another code paragraph. In the old days, you would need some work to do by hand in order to to tidy up text to make it conform to even the most rudimentary typesetting conventions.

CSS previously gave you some addressing methods that could be used. You could apply styles based on the ID of the DOM Element, or could apply it to all the children of a particular element, or element class, or to all the children of a particular TagType (e.g. A, DIV, P ). CSS2 and CSS3 give you a lot more besides. You can apply a style to the ‘N’th child, or to every other child, whether they be even or odd in the sequence. ( 2, 4, 6, 8, 10 or 1, 3, 5, 7, 9¦) This is just the start. You can match the first element and no others, or the first few. You can specify this in inverse order, from the last element. If you want to disregard the parentage of the elements that you wish to attach a style to, then you can do so, and just specify elements by their sequence irrespective of their place in the hierarchy. At last, all the current browsers support this. Even Internet Explorer 9 will do it! The full list of these CSS addressing methods is contained in Simple-Talk’s  XPath, CSS, DOM, Selenium. Rosetta Stone and Cookbook. wallchart here, together with the equivalent XPath and javascript  syntax.

To illustrate how this simplifies perfectly conventional formatting, we’ll take a very simple example. You have a list and you’d like to enhance it so the background colour on alternating rows is different.

  1. Solomon Grundy,
  2. Born on a Monday,
  3. Christened on Tuesday,
  4. Married on Wednesday,
  5. Took ill on Thursday,
  6. Grew worse on Friday,
  7. Died on Saturday,
  8. Buried on Sunday.
  9. This is the end of Solomon Grundy.
  1. Solomon Grundy,
  2. Born on a Monday,
  3. Christened on Tuesday,
  4. Married on Wednesday,
  5. Took ill on Thursday,
  6. Grew worse on Friday,
  7. Died on Saturday,
  8. Buried on Sunday.
  9. This is the end of Solomon Grundy.

What do you mean, they look the same? We’re talking about CSS3, so you will need an up to date version of any of the browsers. Even Internet Explorer should show the stripes in the right-hand list unless IE has gone quietly into ‘compatibility mode’. (look for the grey border around the browser window). See here for a fix. Hopefully, with an up-to-date browser, you’ll see how we’re using the CSS3 syntax for applying different styles to alternating elements

Both the CSS and the HTML are very simple.  The CSS has something that looks a lot like a CSS2 pseudo-class, and in fact extends the idea. It is called a structural pseudo-class.

And the HTML  has no formatting beyond a couple of DIVs to position the two lists side by side.

For a simple example like this, it is easier, and more backward-compatible, to simply assign a class to alternating elements to achieve the effect. However, you often can’t do this, and where you are receiving an XHTML fragment from a source that knows only about the data rather than your requirements to make it easy on the eye, then it obviates the task of altering XHTML, which can get messy.

When content is generated from a database, it is very quick to supply it to the application as an XHTML fragment, using the XML extensions to SQL syntax. The actual data structure you use depends on the structure of the data. Most commonly, it will be a table, but I’ve used lists, Dictionary lists, and nested DIVs as well as tables. Here is a quick example of a table generated from AdventureWorks.


SET @query = ‘<table>

    <caption>AdventureWorks Employees</caption>

    <tr><th>Employee Name</th><th>Phone</th><th>Email</th></tr>’

+ REPLACE(CAST((SELECT TOP 20 –purely for demonstration purposes

      td =   COALESCE(Title + ‘ ‘,”)

             + COALESCE(firstname + ‘ ‘,”)

             + COALESCE(Middlename+’ ‘,”)

             + Lastname, ”,

      td = Phone,”,

      td = EmailAddress




        <tr>’) + ‘</table>


SELECT  @Query 

Tables are much easier to read if one can apply subtle shades to the backgrounds or borders to delineate columns and rows. You can even do rather nice ‘warp and weft’ effects to make it easier for the eye to follow down a column or along a row. Additionally, any Excel user will know how important the column and row headers are to help people understand data in tables. If you generate an XHTML table from SQL Server, you don’t get an option to apply different classes to different columns or rows. Why should you need to do so, when the way that a table is displayed is none of the business of the database.

We can leave the HTML structure as it is without assigning any classes. We just create a style sheet. Here is an example. I’m using bitmaps instead of flat background colors just because I prefer the slightly livelier effect.

And the HTML source of the table (truncated) looks like this

..and it should look a bit fancier…

Employee Name Phone Email
Mr. Gustavo Achong 398-555-0132
Ms. Catherine R.Abel 747-555-0171
Ms. Kim Abercrombie 334-555-0137
Sr. Humberto Acevedo 599-555-0127
Sra. Pilar Ackerman 1 (11) 500 555-0132
Ms. Frances B.Adams 991-555-0183
Ms. Margaret J.Smith 959-555-0151
Ms. Carla J.Adams 107-555-0138
Mr. Jay Adams 158-555-0142
Mr. Ronald L.Adina 453-555-0165
Mr. Samuel N.Agcaoili 554-555-0110
Mr. James T.Aguilar 1 (11) 500 555-0198
Mr. Robert E.Ahlering 678-555-0175
Mr. François Ferrier 571-555-0128 franç
Ms. Kim Akers 440-555-0166
Ms. Lili J.Alameda 1 (11) 500 555-0150
Ms. Amy E.Alberts 727-555-0115
Ms. Anna A.Albright 197-555-0143
Mr. Milton J.Albury 492-555-0189

I’ve done all this with just the nth-child CSS Selector. However, there are others that can be used to select particular elements of ‘naked’ structures.  The Rosetta Stone and Cribsheet gives a much fuller list of selectors, pseudo-classes and pseudo-elements, particularly of attribute addressing (which doesn’t help us as our tables haven’t got attributes). Before you use them, check on the Quirks Mode site for compatibility with current browsers, and check on The W3.ORG site for the details, and plenty of examples of their use

Selector Function Example What Example does
* selector Selects all elements * {font: 9pt Arial, Helvetica, sans-serif;} Makes all elements have the specified font
> selector Selects direct children of an element div.listing > p{ margin: 0;
   font: 11pt “Courier New”,Courier,monospace; }
Assign style to all paragraphs directly within div.listing
+ selector Selects the following sibling of an element h2 + p {margin-bottom: 10pt} make first paragraph after n H2 heading with a bottom margin of 10pt
[attr] selector Selects an element with a certain attribute or attribute value p[align=right] {float: right;border:
1px solid silver; text-align: left;
padding: 15px; width:300px; }
float any text with the attribute ‘align=”right”‘ to the right  in a box 300px wide
:before and :after  Insert content before or after an element p.quote:before { content: open-quote; }

p.quote:after { content: close-quote; }

put a quote before and after the start of any paragraph whose class is ‘quote’\
:first-child and :last-child Select first and last children of an element table td:first-child{ font-variant: small-caps; }
table td:last-child{ font-variant: small-caps; }
make the first and last column  of the table render in small-caps.
:first-line and :first-letter : Select the first line or the first letter of an element p.start:first-letter
  { line-height: 100%; float: left;
     font-size: 280%; }
p.start:first-line { font-variant: small-caps; }

makes a large first letter, floated to the left, and puts the fist line in small-caps
~ selector Selects the general next sibling(s) of an element h3 ~ p
give every paragraph following an H3 a margin of 40em
:first-of-type The first sibling element of its type  td:first-of-type { font-weight: bold; } makes the first column in a table bold
:last-child the last sibling   td:last-child { font-variant: small-caps; } makes the last column text smallcaps
:last-of-type The last  sibling element of its type  td:last-of-type
  { font-variant: small-caps; }
makes the last column text smallcaps
:only-of-type The only child of its type  td:only-of-type {font-weight: normal; font-variant: normal;} make the font normal if there is only one column
:contains(‘text’)  now withdrawn for some reason!
:empty Empty elements (without content) td:empty { background: silver; } give a cell a silver background if it contains no text
:nth-child(an+b))  Selects elements according to a formula specifying that the  element has an+b-1 siblings before it in the document tree  tr:nth-child(odd) {background-color:gray;}
p:nth-child(4n+1) { color: navy; }
p:nth-child(4n+2) { color: green; }
 p:nth-child(4n+3) { color: maroon; }
p:nth-child(4n+4) { color: purple; }
make the odd rows of a table have a gray background
the next four examples alternate between four colours
:nth-last-child(an+b)) Selects elements according to a formula specifying that the  element has an+b-1 siblings after it in the document tree  tr:nth-last-child(-n+2) 
The last two rows in the table should have a gray background
:nth-of-type (an+b))  Selects elements according to a formula specifying that the  element has an+b-1 siblings before it with the same name in the document tree  img:nth-of-type(2n+1) { float: right; }
img:nth-of-type(2n) { float: left; }
float alternating images in the same document level, left and right
:nth-last-of-type(an+b)) Selects elements according to a formula specifying that the  element has an+b-1 siblings after it with the same name in the document tree

make the odd rows of a table(Make the first and last row have a background of silver

Let’s take a different example. Here is a PowerShell script. It is getting information about the SQL Server-related services that are running on a list of servers that I’ve supplied just to check that they are running OK. Whatever. The point is that I’ve once more got stuck with a table result that looks pretty difficult to format nicely.

You’ll get an output somewhat like this (I’ve kept it short for demo purposes)

…but we can create something rather easier on the eye by defining some styles.

that will give this, once the style has been applied to the table

__Server Caption Description Name Status Started StartMode
PHILFTEST SQL Server (SQL2005) Provides storage, processing and controlled access of data and rapid transaction processing. MSSQL$SQL2005 OK True Auto
PHILFTEST SQL Server (SQL2008) Provides storage, processing and controlled access of data, and rapid transaction processing. MSSQL$SQL2008 OK True Auto
LTPHILF SQL Full-text Filter Daemon Launcher (MSSQLSERVER) Service to launch full-text filter daemon process which will perform document filtering and word breaking for SQL Server full-text search. Disabling this service will make full-text search features of SQL Server unavailable. MSSQLFDLauncher OK False Manual
LTPHILF SQL Server (MSSQLSERVER) Provides storage, processing and controlled access of data, and rapid transaction processing. MSSQLSERVER OK False Manual
LTPHILF SQL Server Agent (MSSQLSERVER) Executes jobs, monitors SQL Server, fires alerts, and allows automation of some administrative tasks. SQLSERVERAGENT O K False Manual

Putting it all together

Curiously, after years of happily using Internet Explorer as a simple way of displaying data as part of a scripting process, I initially  fell foul of using Internet Explorer 9 to display HTML5 and CSS 2/CSS3. Here is the script that I currently use to automatically display this, and any other grid of data in PowerShell. The principle is the same for every other scripting language.

If you can’t see CSS2-3/HTML5  views of a page.

If you can’t see all those CSS2-3/HTML5  effects in a page in IE8 or IE9, it probably means you’re in ‘compatibility mode’.  If your browser window has a thin gray border, shucks, you’re in Compatibility mode.

The developers of Internet Explorer were faced with a change of direction towards HTML/CSS standards-compliance after years of attempting an ultimately futile policy of creating their own de-facto browser standard. In order to create a browser that could still render websites that were written to the Internet Explorer standard,  with IE8 and IE9, they have had to introduce a notion of ‘compatibility mode’ which is extraordinarily elaborate and even involves the maintenance of a register of  the ‘compatibility’ of various websites. The intricacies of this mechanism would wear out the patience of even the keenest reader so I’ll say little more other than to say that one can force the browser to stop fleeing standards-mode like a frightened rabbit by …

  • selecting, from the menu, TOOLS -> Compatibility View Settings, and unchecking ‘display intranet sites in compatibility view‘ (and, if necessary,  ‘display all websites in compatibility view’ ).
  • From the menu, Tools -> Internet Options. Click on  the advanced tab, Look for the browsing section, and in there find ‘automatically recover from page layout errors with Compatibility view‘. Unclick it.
  • If all else fails… Hit F12 in order to get into the ‘developer pane’. Here, you will see the last two menu item ‘Browser Mode and ‘Document Mode’. Make sure that these are set to IE9.

I know of no way in scripting to force IE9 to read a dynamic page in standards mode, other than by using the HTML5  doctype (<!DOCTYPE HTML>) in the page you write to the instance. In the script above, I’ve used guile rather than science.


The use of the CSS structural pseudo-class and pseudo-elements hold out the hope that we cen do what we want with the rendering of HTML structures without having to add a lot of classes to the elements, but instead just doing it by specifying their position in the DOM. I have to admit that I miss the suddenly-withdrawn :contains pseudo class which for the first time allows us to specify an element by its content. We hope that wider counsels prevail. For those of us who are having to render such XHTML fragments as tables and lists, generated from PowerShell and SQL, these extensions to CSS are a real boon.

How you log in to Simple Talk has changed

We now use Redgate ID (RGID). If you already have an RGID, we’ll try to match it to your account. If not, we’ll create one for you and connect it.

This won’t sign you up to anything or add you to any mailing lists. You can see our full privacy policy here.


Simple Talk now uses Redgate ID

If you already have a Redgate ID (RGID), sign in using your existing RGID credentials. If not, you can create one on the next screen.

This won’t sign you up to anything or add you to any mailing lists. You can see our full privacy policy here.