[selectors] Proposal: :n-children() selector

(Apologies to the maintainer of the list — I sent this twice without being subscribed. Please delete those pending copies.)

I'd like to propose addition of functionality to CSS selectors level 4 to style an element conditionally based on the number of direct descendants it has. 

Note that this is not entirely new functionality as you can emulate it in some cases by bending the current selectors around a bit. [1][2][3]

Justification
=============

This is useful building block for responsive, script-free structures on a page that dynamically adjust to the number of elements they contain [4][9]. See the "example use case" section below for specific examples where it would allow novel behaviour or simplify an existing case.

As mentioned before, this is not entirely new functionality. The case where one styles children based on the number of elements directly contained by their parent can be achieved today by specifying both :nth-child and :nth-last-child on a child and the general sibling selector (~). [1][2][3] More complicated cases (e.g.: the fourth, seventh, tenth, etc. child of n, where n+2 is a multiple of three) can be difficult to reason through and not easy to parse when found in an existing stylesheet.

What this selector does is make this existing functionality easier to read and write, while also opening up additional possibilities to style an element based on the number of children it has. This selector is to :empty and :not(:empty) as :nth-child/:last-child is to :first-child/:last-child.

This selector adds a new case where an element's style may change as an HTML document is streamed from a remote server. I believe there is a precedent for this in the existing :nth-last-child() and :empty() selectors which may change state as the document is loaded, and the browser should use the same strategy for the new selector.

Proposed syntax
===============

The proposed new selector looks and acts as similar to the existing :nth-child() selector as possible:

:n-children(An+B [of S?])

An+B [5] represents a formula determining the number of children in an element. "even" and "odd" are also valid in its place.

The behaviour of "of S" is analogous to its definition for :nth-child in CSS Selectors Level 4.

For a developer who understands nth-child, the result of the examples here should be unsurprising and for the most part, analogous to their nth-child counterpart:

* :n-children(2) 
    matches an element with exactly two children, of any type

* :n-children(2 of div.important) 
    matches an element with exactly two elements that match div.important (other elements are not considered)

* :n-children(even) 
    matches an element with an even number of children (including zero)

* :n-children(10n-1) 
    matches an element with 9, 19, 29, etc children

* :n-children(10n+9) 
    matches an element with 9, 19, 29, etc children

* :n-children(10n+-1) 
    syntactically invalid, and would be ignored

* :n-children(n+1) 
    matches an element with 1 or more children (equivalent to :not(:empty))

* :n-children(n+2) 
    matches an element with 2 or more children

* :n-children(-n+2) 
    matches an element with 2 or fewer children

Example use cases
=================

This particular selector is not entirely novel. These are real-world use cases that would benefit from the addition of an n-children() selector:

* Responsive image galleries that adjust cleanly to the number of elements [4]
* Shrinking the height of dynamically added elements as more are added [6]
* Turning on "overflow" UI when reaching a certain number of children (see example below)
* Simplified table sizing for small numbers of known cases [7] 
* Responsive/auto-sizing widget layouts [8]
* List as a fully-justified grid [9]

Example CSS
===========

A container that shows/hides an "overflow" toggle button if five or more .child items are contained within:

   div.container button.overflow {
       display: none;
   }

   div.container:n-children(n+5 of .child) button.overflow {
       display: block !important;
   }

   div.container:not(.expanded) .child:nth-child(n+5 of .child) {
       display: none;
   }

Auto-sizing, fully-justified sidebar widgets (half-sized in pairs, first widget full-sized if there is an odd number):

   div.sidebar .widget {
       width: 50px;
       height: 50px;
       display: inline-block;
   }

   div.sidebar:n-children(odd of .widget) .widget:first-of-type {
       width: 100px;
       height: 100px;
   }

Alternatives
============

1. Rather than adding a selector to the parent, we can add a selector to an element that would select based on the number of siblings (i.e.: :n-siblings()). This has the advantage of being similar enough to existing code that the impact on CSS engines would be less than the full proposal.

2. Do nothing (if the case where styling a parent based on the number of children is not deemed important enough to add this functionality to the spec). For users wishing to style children based on the number of siblings, they can continue to use both :nth-child and :nth-last-child to do this.

References:

[1] http://grack.com/blog/2015/01/09/abusing-css3-selectors
[2] http://lea.verou.me/2011/01/styling-children-based-on-their-number-with-css3/
[3] http://andr3.net/blog/post/142
[4] http://grack.com/assets/2015/01/nth-child/image-count.html
[5] http://dev.w3.org/csswg/css-syntax/#anb
[6] http://bytesizematters.com/
[7] https://plus.google.com/100749189589213497870/posts/dgz5imd3jDE
[8] https://wordpress.org/support/topic/media-query-breakpoints
[9] http://codepen.io/heydon/pen/bcdrl

Received on Tuesday, 13 January 2015 18:52:54 UTC