-
Notifications
You must be signed in to change notification settings - Fork 757
Description
The current Selectors 4 draft introduces the :nth-col() and :nth-last-col() selectors, as a way to select grid elements (including table cells) that exist within a given column. However, no similar pseudo-classes exist for rows. The draft justifies this on the grounds that tables and similar grid structures are typically row-major; however, that doesn't account for things like the rowSpan attribute on table cells.
Consider the following table:
<table>
<tbody>
<tr>
<td rowSpan="2"> A </td>
<td> B </td>
</tr>
<tr>
<td> C </td>
</tr>
</tbody>
</table>
<!--
layout:
A B
A C
-->In this scenario, there is no way to tell that the "A" cell extends to the bottom of the table. Tables are row-major, but we can only tell what row "A" starts in; there's no way to tell what row it ends in. This causes a few problems:
- Setting a
border-radiuson a table will round off the table's corners, but not the corners of its corner cells; the cells remain rectangular and are drawn overtop the table's rounded corners. To give a table proper rounded corners, we must setborder-top-left-radiusand friends on the top-left, top-right, bottom-left, and bottom-right cells. However, we can't reliably identify the bottom-left cell in the above table using selectors alone. - Setting
border-collapseon a table will prevent you from usingborder-radius, and may additionally prevent other effects that rely on separated borders to work. In order to work around this, you have to leave the borders separated, and then emulateborder-collapseby selectively setting some border widths to zero in order to manually collapse those borders. However, if a cell is rowspanned from a non-bottom row down into the bottom row, you can't reliably detect this, and so you can't collapse its bottom border into the table's bottom border.
Typically, we would detect when a cell is on any edge of the table using the following selectors:
/* Top edge: */
table > :is(thead,tbody,tfoot):first-child > tr:first-child > :is(td,th) {}
/* Bottom edge: */
table > :is(thead,tbody,tfoot):last-child > tr:last-child > :is(td,th) {}
/* Left edge: */
table > :is(thead,tbody,tfoot) > tr > :is(td,th):first-child {}
/* Right edge: */
table > :is(thead,tbody,tfoot) > tr > :is(td,th):last-child {}The selectors above fail for rowspanned cells, and for cells adjacent to them (i.e. "B" in our example above would wrongly test as a leftmost-column cell).
With the column pseudo-classes introduced in Selectors 4, I believe the syntax would now be:
/* Top edge: */
table > :is(thead,tbody,tfoot):first-child > tr:first-child > :is(td,th) {}
/* Bottom edge: */
table > :is(thead,tbody,tfoot):last-child > tr:last-child > :is(td,th) {}
/* Left edge: */
table :is(td, th):nth-col(0) {}
/* Right edge: */
table :is(td, th):nth-last-col(0) {}This fixes the "B" case; however, the rowspanned "A" cell in our example table above still won't test as being on the bottom edge. We would need :nth-row() and :nth-last-row() counterparts in order to cope with rowSpan.
I offer a more detailed breakdown of use cases, including links to examples, in the issue I originally opened in the WHATWG HTML repo by mistake. Those examples were written before I was aware of the yet-to-be-implemented :nth-col() and :nth-last-col() selectors, but I believe they still adequately demonstrate the utility of row-based selectors.