Fortunately, this is something that comes together in Tapestry in almost no code:
This is what it looks like in a template:
And here's the markup it produces:
It is always important to me that Tapestry not get in the way: frameworks that lock you down and prevent you from accomplishing your goals should not be used. Tapestry has a long history, and there are certainly many areas that could have been implemented differently, or simply not implemented at all, but it's nice that most edge cases, like this one, have a simple solution.