On a recent project I needed to build the following design, which is a simple list of images.

Example grid of six images with borders

The challenge with this design was to have the border above the individual items for the first and last row of images, not a single full-width border above and below the entire list.

When coding, I find it incredibly useful to write out, in plain English, what I want to achieve and how I might go about achieving it. For this problem I wanted to;

  • Add a border above and below the set of images,
  • If the list of images formed a perfect grid (3, 6, 9 etc) and the browser supports CSS3 :nth-child and :nth-last-child selectors, then remove the full-width borders and set the border on the first and last rows.

To solve this problem I used a combination of the HTML5 data attributes, CSS3 :nth-child and :nth-last-child selectors and Modernizr to test for the CSS3 support needed.

Firstly, I added two HTML5 data attributes to the container. One which denotes whether the set of images is a “perfect grid” — data-grid=”true” — and a second which was the total number of images inside the list — data-total=”6”.

Secondly, I needed to check whether the required CSS3 selectors I wanted to use were supported by the browser. I include Modernizr on projects, which has checks for a lot of useful HTML5 and CSS functionality, but it didn’t include any tests for the :nth-child and :nth-last-child selectors — but it does include a test API. Below are the two tests I wrote:

Modernizr.testStyles('#modernizr div:nth-child(1) {height:10px;}', function (elem, rule) {
   Modernizr.addTest('cssnthchild', function () {
       return elem.getElementsByTagName('div')[0].offsetHeight == 10;
   });
}, 3);

Modernizr.testStyles('#modernizr div:nth-last-child(1) {height:10px;}', function (elem, rule) {
   Modernizr.addTest('cssnthlastchild', function () {
       return elem.getElementsByTagName('div')[2].offsetHeight == 10;
   });
}, 3);

Modernizr then adds the appropriate class to the HTML, depending on whether the tests return true or false. In the case of the two tests above, ‘cssnthchild’ and ‘cssnthlastchild’ are added if :nth-child and :nth-last-child are supported. If they’re not supported, they’re prefixed with ‘no-’, eg ‘no-cssnthchild’.

Finally, we can use these classes in the CSS to remove the solid full-width borders and replace them with borders above the first row of images and below the last row of images. The CSS looks something like this (note, the CSS has been written in a verbose manner for ease of understanding and can be written more succinctly):

ul.images {
   padding: 5px 0 0;
   border: 0 solid #000; border-width: 1px 0;
}
html.cssnthchild.cssnthlastchild ul.images[data-grid="true"] {
   padding: 0;
   border-width: 0;
}
html.cssnthchild.cssnthlastchild ul.images[data-grid="true"] li:nth-child(-n+3) {
   padding-top: 5px;
   border: 0 solid #000; border-width: 1px 0 0;
}
html.cssnthchild.cssnthlastchild ul.images[data-grid="true"] li:nth-last-child(-n+3) {
   padding-bottom: 5px;
   border: 0 solid #000; border-width: 0 0 1px;
}

This works, except for when there are only three images. This is why the HTML5 data attribute data-total=”3” was added. Using the attribute selector, targeting the list which only contains three images, a top border needs to be added alongside the already added bottom border. The following CSS solves this:

html.cssnthchild.cssnthlastchild ul.images[data-total="3"] li:nth-child(-n+3) {
   padding-top: 5px;
   border-top-width: 1px;
}

See the final solution.