I have long sought a web-native way to achieve sidenotes with CSS, and it turns out Eric is in the same boat. The new Anchor Position API in CSS is seeking to solve that problem, and whilst it remains very experimental right now (and support is only in early stages) it looks extremely promising. This is a brilliant write-up of how to achieve the precise thing I keep on attempting, as well as a general overview of where this functionality is (currently) headed.
The gist is that an anchor comes in two parts:
- A named in-flow element (or elements, which is cool);
- And an out-of-flow element that will be anchored to it.
You define the anchor point for the in-flow element using typical CSS naming patterns:
main {
anchor-name: --main-content;
}
sup {
anchor-name: --reference;
}
And then reference this from your out-of-flow element in the same way that you would use absolute positioning, though you can also specify an "edge" to use as the connection point:
.sidenote {
position: absolute;
left: anchor(--main-content right); <-- this will attach to the previously named element, and align with the right-hand edge.
}
(I'm not sure whether logical properties work here – i.e. inline-end
rather than right
– but I hope so.)
Eric points out that one of the cool superpowers of the current spec is that you can anchor an element to multiple named anchors:
Yes, I’m anchoring the sidenotes with respect to two completely different anchors, one of which is a descendant of the other. That’s okay! You can do that! Literally, you could position each edge of an anchored element to a separate anchor, regardless of how they relate to each other structurally.
So this kind of pattern, where you anchor a sidenote both to the main content block and to a footnote reference in the text is extremely easy (you can also use calc to add spacing between anchored elements):
.sidenote {
position: absolute;
top: anchor(--reference);
left: calc(anchor(--main-content right) + 0.5rem);
}
But because these elements are all out-of-flow, the browser has no issue with them overlapping. So how do you prevent that? How do you keep your sidenotes nicely stacked? Well, the spec states that if multiple elements have the same anchor name then the anchor point should be assumed as the closest element to the positioned item. That gives us some pretty cool behaviour, because it means we can turn all of the sidenotes into anchor points themselves, give them all the same anchor name, and then each one will be able to position itself relative to the last sidenote in the DOM 🤯 (Eric points out this technique was first discovered by Roman Komarov who has a very clever CodePen detailing some of these tricks.)
The specification says the named anchor that occurs most recently before the thing you’re positioning is what wins.
So you can do something like this and prevent overlapping from ever occurring:
.sidenote {
position: absolute;
anchor-name: --sidenote;
top: max(anchor(--reference), calc(anchor(--sidenote bottom) + 0.5rem);
left: calc(anchor(--main-content right) + 0.66em);
bottom: auto;
max-width: 23em;
}
The max
function here is doing a lot of heavy lifting, ensuring that a sidenote is either placed alongside the superscript citation link in the text or just below the previous sidenote (with some padding), whichever is greater:
It can all be verbalized as, “the position of the top edge of the list item is either the same as the top edge of its anchor, or two-thirds of an em below the bottom edge of the previous sidenote, whichever is further down”.
The browser knows how to do this because the list items have all been given an anchor-name
of --sidenote
(again, that could be anything, I just picked what made sense to me).
Given my setup, this means an anchored sidenote will use the previous sidenote as the anchor for its top edge.
Frankly, this is incredible. I love it 😍 Though browser support is likely to be slow and the spec is still changing, so this may not ultimately be what gets shipped. Personally, my one concern here (which Eric briefly mentions) is accessibility. Much like Grid positioning, this feels like we need to be able to control the reading order of content before this can be considered a truly useable approach. And because anchoring is being intrinsically linked to absolute positioning, it makes me worried that even if we do get reading order control in the future, it won't apply here. So in that sense, I'd almost prefer to see this shifted to a new position value (e.g. position: anchored;
) which can have some better, more accessible behaviour baked in.
Remember that all this is experimental, and the specification (and thus how anchor positioning works) could change.