You can find a summary of the code updates in this pull request .
Step 1: Hide the table of contents for narrow viewports
Here we’re going to need a media query that applies different CSS rules when the screen width is >=70rem.
I like to go with a “mobile first” approach , where the default CSS is for narrower screen widths (like mobile devices), and then extra rules are applied within media queries for wider screen widths.
For mobile, the styles are pretty similar to the original BlogPost.module.css file. I reverted to that code temporarily, and added a rule that the table of contents should not be visible.
BlogPost.module.css
Then I added all of the grid styling for screen widths >=70rem:
BlogPost.module.css
This almost works — the problem is, all previous CSS rules apply unless they’re overwritten. So the display: none;
is still applying to the table of contents. It won’t show up until we override that with display: revert;
(the revert
means “whatever the value was before it got changed” – in this case, the change was to none
).
BlogPost.module.css
Step 2: Stickiness
Sticky positioning has a lot of gotchas. I have spent many a tense debugging session trying to get an uncooperative element to stick.
Your first attempt at getting the table of contents to stick might have been similar to mine. In src/components/ToC/ToC.module.css, I added position: sticky
. Easy, right?
ToC.module.css
The problem: this doesn’t work. 🤬
Here’s what’s going on. We’re asking the ToC
component to “stick” within its parent. In this case, the parent is the aside
element in the BlogPost
component.
If you add a border to the .wrapper
class for ToC
(blue dots in these screenshots) and another one to the aside.toc
class for the aside
in BlogPost
(solid red), this is what it looks like:
Sticky positioning essentially proposes this: keep the sticky element…
- no closer than the specified distance to the viewport (in this case,
1rem
from the top) - unless that would mean the sticky element would leave its parent element. In that case, ignore the stickiness.
As you can see in the screenshot above, the sticky element (the table of contents) can’t stay within 1rem
of the top of the viewport without leaving its parent (the aside
). The table of contents can’t change its position relative to its parent at all without leaving its parent, because they’re the same size. Wherever the aside
goes, the table of contents is along for the ride.
The solution here is to expand the size of the aside
so that it’s as tall as its container (the .wrapper
grid for the BlogPost
component). We can do that with a CSS rule of height: 100%
for the aside
’s class (aside.toc
). In BlogPost.module.css
:
BlogPost.module.css
Now the table of contents has room to change its position within its parent, aside
.
Step 3: Account for the header height
If you’ve been coding along, you might have noticed that your app doesn’t quite look like the images above. The table of contents is sticking 1rem
from the top of the viewport, which means it gets buried under the header like this:
We need to account for the header height in the top
distance from the viewport. Fortunately, there’s a CSS custom property for the height of the header in src/app/globals.css. We can add it to the 1rem
by using calc .
ToC.module.css
Voila! We’ve achieved the goals. See you in the next workshop!