A TOC Spine That Fades Like a Notes-App Timeline
Tired of the stiff left border on my blog TOC, I swapped it for a Tailwind gradient spine that fades downward. Pure CSS utility, plus a note on the bg-gradient-to-* rename in v4.
The flat border that made my TOC feel boxed in
I opened the post page and looked at the table of contents. The child list under each heading still ran that flat border-l straight down the side, and the last sub-heading just dangled on a hard vertical rule. Boxed in, stiff, not the soft timeline vibe I wanted.
I first thought I'd need a pseudo-element or maybe a splash of JS to fade the line out. But then I remembered CSS gradients can run on a 1px element just fine. The fix turned out to be pure utility classes, no script.
Swapping the border for a fading spine
The markup I changed in BlogToc.tsx looked roughly like this:
<div className="relative ml-[11px] border-l border-border pl-3">
{children.map((child) => (
<a className="relative block ...">
<span className="absolute -left-3 top-1/2 h-px w-2.5 bg-border" />
{child.text}
</a>
))}
</div>
<div className="relative ml-[11px] pl-3">
<span
aria-hidden="true"
className="absolute top-0 bottom-1 left-0 w-px bg-gradient-to-b from-primary/40 via-border to-transparent"
/>
{children.map((child) => (
<a className="relative block ...">
<span className="absolute -left-3 top-1/2 h-px w-3 bg-border" />
{child.text}
</a>
))}
</div>
The spine is a 1px-wide element stretched from top-0 to bottom-1, with a background gradient fading from primary at 40% opacity, through the border color, to transparent at the bottom. A CSS linear-gradient defaults to the to bottom direction [1], which maps to 180deg [1] — exactly what we want: the top is visible, the bottom dissolves.
One detail I had to get right: the spine height is top-0 bottom-1, not bottom-0. The last child's node stub sits at top-1/2, and if the spine ran all the way down it would poke past that final stub and look like a stray tail. Leaving a bottom-1 gap makes the spine "stop" right at the last stub instead of overshooting it.
I'm on tailwindcss 4.3.3. The official v4 docs say the gradient direction utility is now bg-linear-to-b instead of the old bg-gradient-to-b [2], and the upgrade guide itself shows bg-linear-to-r in its example [3]. But locally both classes still compile to valid CSS because the legacy one is aliased — so my bg-gradient-to-b isn't broken, just not the documented v4 way. I kept it since it works, but on a fresh v4 project you'd reach for the new utility.
Each child keeps its node stub, now slightly longer (w-2.5 → w-3) so it connects cleanly to the spine with no gap between the row and the line. And because the spine is purely decorative, I tagged the <span> with aria-hidden="true" so screen readers don't announce it as a line. No JavaScript anywhere in this effect.
I personally like the gradient spine way more than a plain border for a TOC. A plain border reads like legal document furniture; the fading line tells the eye "the list ends here" without slamming down a rigid rule. Shipped in commit 2eaa16e with the message style(blog): timeline spine on TOC child list, and the last sub-heading in my TOC finally gets to breathe.
This is purely cosmetic, not an accessibility win. Under forced-colors or grayscale the spine still looks different from a plain border, but it never makes the list harder to read. The same absolute + w-px + gradient trick works anywhere you need a connector: progress indicators, event timelines, thread lines in a comment tree. The pattern is small enough that I reach for it instead of a component.
The whole change is about twelve lines of component code, and the only behavioral difference is visual. That's the kind of polish I'd normally skip, but the TOC is on every post page, so the stiffness was showing up everywhere.