Short Tutorial to Style the Space Between Grouped Table Rows


Woman's hand typing code on a laptop

Recently, I confronted the challenge of styling related table rows to appear as distinct groups within one shared table. My first instinct was to look to border-collapse, but that rule is all or nothing; you can't use it on only part of a table. So I knew I'd have to get creative in other ways.

Through experimenting and discussion with other UXD team members, we crafted the following methods.

Using border-style: double;

The simplest way to create that extra separation between grouped rows is to use a double border on the top of each cell in the first-in-the-group rows:

This works, but it doesn't offer much control. Increasing the `border-width` eventually will increase the borderline widths as well as the space between them. So if you want lines that are only 1px thick with more than a 2px gap between them, then it won't suffice.

<tbody> groups with pseudo content

Another method to add space between row groups is to leverage multiple tbodys to group the rows in the markup. Doing this will allow you to insert pseudo elements between them with display: table-row;:

This is a great solution if you have the flexibility to structure your table this way. But it may not be possible if you're using a pre-built table component.

One more thing to note about this method is browser differences for the pseudo-element: it spans the whole table width in Firefox, but not in Chrome. Try enabling the commented background-color in the codepen to see what I mean. So while it's perfect for adding space between tbody tags, it may not be the best choice if you need to include extra borders.

<td> pseudo content & thick border

If your table can't have multiple <tbody> tags, and you need more control than what border-style: double; offers you, then this method may suit your needs. It's a bit roundabout, but this idea is:

  1. Apply a thick border-top to the cells of each first-in-the-group row.
  2. Add pseudo content in those same cells and position it on top of the thick border.
  3. Use borders on the pseudo-content to replace the visible border that was sacrificed in step 1.

There are a few pitfalls to be aware of for this method. First, if your table cells have side borders, or if they have a background color, then the pseudo content will need to have a background color. This is because the side borders tend to bleed into the adjacent transparent borders. With <td> background colors, there is an old and persistent bug in Firefox where they paint on top of borders. Adding a background color to the pseudo content can cover up both of these problems if the background color can match the page's background color.

Also, be sure you're putting the pseudo content into the table cells and not the table row. Adding pseudo content as a child of a table row will throw off the flow of the table cells.

The final thing to keep in mind is users of Windows High Contrast Mode. Those users will see all borders (including transparent ones) rendered as the color set by their theme. So, consider if their experience would be confusing or hard to interpret as a result of using this method.

Inserting blank <tr>s

If all else fails, you could consider adding empty table rows above each first-in-the-group row.

The attribute aria-hidden="true" will prevent screen readers from unnecessarily announcing the node, while the empty <td colspan="3"></td> will ensure that the HTML validates in the W3C Markup Validation Service.

After weighing the pros and cons of each method, my personal preference was to use the third choice of pseudo content with transparent borders. My table didn’t have competing background colors or borders to worry about, so those caveats were not obstacles for me. But what works for you might be different. Try each of these options to see what you like best.

DockYard is a digital product consultancy specializing in user-centered web application design and development. Our collaborative team of product strategists help clients to better understand the people they serve. We use future-forward technology and design thinking to transform those insights into impactful, inclusive, and reliable web experiences. DockYard provides professional services in strategy, user experience, design, and full-stack engineering using Ember.js, React.js, Ruby, and Elixir. From ideation to delivery, we empower ambitious product teams to build for the future.