Over the past weeks, I’ve been building this blog and writing articles for it. I’ve spent a lot of time thinking about the reader. Who are they? Why are they here? It’s often hard to know how much detail to go into and how much background knowledge to assume.
On the one hand, people are busy. Attentions are short and time is precious. I’d do well to keep articles lean, focussed and to the point. But that misses the point. People are here for enjoyment and just presenting the cold hard facts isn’t going to cut it.
I’ve settled on an approach I hope will cater to a variety of readers. I keep the main body of content concise and on topic but enrich it with commentary on the side. This leaves room for elaboration, tangential remarks and all the whimsy I can mustard.
However, this commentary hides complexity. When viewed on a large-enough screen, it’s presented in a sidebar next to the main content. Ideally, comments sit as close as possible to the word or phrase they refer to. This helps flow and improves continuity.
In this article, I’ll explain the mechanics of how this works in my React app. As you’ll see, there’s a lot to consider to make a robust, responsive and semantic solution.
Putting React to one side for a moment, let’s first consider how to structure the DOM. HTML5 has the perfect element for our needs:
The HTML
<aside>
element represents a portion of a document whose content is only indirectly related to the document’s main content. Asides are frequently presented as sidebars or call-out boxes. – MDN web docs
That leaves the question of whether to group all commentary together into a single <aside>
or to have one per comment. I
decided on the latter as I think this is more meaningful. Comments are
independent of each other so the DOM should reflect that.
Here’s how that looks in code:
This should be friendlier to search engines, too. Related content is closer together.
As you can see above, there’s no DOM element for the sidebar. I think of a
sidebar as a way to present <aside>
elements. It really ought to be a
presentational concern.
There are a number of CSS techniques we could use to create a sidebar. There’s flexbox, floats, relative and absolute positioning. This seemed simplest to me:
This works by splitting the container in two. The sidebar comments live in the
space created by the padding-right
rule. They use absolute positioning which
removes them from the document’s flow and right: 1rem
to place them in the
padded region.
We can left-position the <aside>
elements within the sidebar by setting their
width. This is calculated as 50% - 2rem
to create the illusion of
padding - by leaving an equal amount of space on the left and right. Here’s how
it looks:
This isn’t quite right because the comment is placed next to the second
paragraph instead of the first. We could fix it by moving the <aside>
above
the first paragraph but then the DOM would be out of order and wouldn’t read
correctly on mobile.
To make matters worse, we also want to be able to position commentary next to specific words or phrases. For example:
We can use absolute positioning for this but it’s not so straightforward. The paragraph is free-flowing text which means it’s hard to predict where the words will be. It depends on the width of the browser, which font is loaded and its line height.
We could change the DOM and move the <aside>
into the text element but again,
that would mean the content’s out of order. We’d end up with a horrible jumble:
<p>In this paragraph, I want to comment on<span> <em>these words</em> <aside>Does red clash with the theme?</aside></span>which are in the middle of the paragraph.</p>
It’s also not good practice to do this. React gives us a warning:
validateDOMNesting(...):
<aside>
cannot appear as a descendant of<p>
.
The fundamental problem is there’s no way in CSS to position elements relative to arbitrary things. It would be neat if we could do something like this, but we can’t:
aside { position: element(#thing); top: 0;}
I spent a while thinking of other ways to do this in CSS. Flexbox’s order
property is interesting because it allows for a difference in how the DOM is
structured to how elements are presented. Ultimately, I couldn’t find a way and
resorted to JavaScript.
In React, I created an <Aside>
component that positions itself relative to
a target element. It encapsulates the behavior of figuring our what its
position should be and listens to events that might invalidate that, such as
resizing the window.
Here’s a first version:
There are really two main parts to it:
align
function calculates the y-coordinate of the target
which is a
reference to a DOM
element. It sets the top
style property to this.useEffect
hook aligns the component when it’s first mounted and binds
a listener to re-align the component if the browser is resized.Here’s how it looks for a real example. Notice the comments move around in the sidebar, repositioning themselves next to their target content.
To use the <Aside>
component, I create a ref to the target content then pass
that in:
As I write this article, I’m using this component a lot! Let’s peek at the code:
If your browser is wide enough, you should see ‘Well this is unexpected’ on the right-hand side of ‘the code’. Otherwise, it’ll appear in the main flow.
In some cases, there’s no word or phrase in the DOM to refer to. This is the
case with images, videos and code blocks that use the <pre>
tag. For these cases, I built an escape hatch that lets me
move comments down by a fixed amount:
<span ref={r = createRef()} /><Aside target={r} moveDown={70}>This aside is moved down 70 pixels.</Aside>
This amount is then added to the offset
:
All told, this serves very well as a means of commenting on variety of things: text, images, code and (as we’ve seen) even comments themselves!
In part two of this article we’ll be refine our component in a number of ways:
Sounds exciting!? I think so... See you in part two.