Skip to content

Conversation

trueberryless
Copy link
Contributor

@trueberryless trueberryless commented Aug 15, 2025

Description

This PR fixes an issue with the Table of Contents being unable to find any heading when the page has a banner with custom styling. This issue is currently tracked in #3047.

The issue is fixed by introducing a temporary IntersectionObserver without any rootMargin (so that it definitely gets an intersection target). This new code only runs when there is no other intersecting target found by the existing IntersectionObserver.

Visit https://deploy-preview-3382--astro-starlight.netlify.app/getting-started/ and make the screen a smaller to see the mobile ToC, then reload and see that a current heading is set.

Please note that this issue could also be resolved in some other ways of course and I'm not sure if introducing a second IntersectionObserver is a good practice just for this edge case. Feel free to suggest cleaner solutions!

Another considered solution

Another solution that came to my mind, but where I figured that it has too many side effects (and therefore I didn't commit to it): By dynamically incorporating the actual height of the banner in the calculation of bottom it would be possible to always guarantee that a heading can be found (which would mean that we don't need a second observer). However, the problem with that approach would be that the observer would then permanently be too big and headings would "too early" be considered current headings. For example: If the banner is really big, headings that scroll into the middle of the screen could potentially be set as the "current" headings in the ToC, which would be misleading.

To demonstrate what I mean with this paragraph in code:

-const bottom = top + 53;
+const bottom = top + 53 + bannerHeight; // assuming we calculate the actual banner height before

Related stuff and todo

Copy link

changeset-bot bot commented Aug 15, 2025

🦋 Changeset detected

Latest commit: ccd778d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@astrojs/starlight Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

netlify bot commented Aug 15, 2025

Deploy Preview for astro-starlight ready!

Name Link
🔨 Latest commit ccd778d
🔍 Latest deploy log https://app.netlify.com/projects/astro-starlight/deploys/68b921f7a1c7d7000835f765
😎 Deploy Preview https://deploy-preview-3382--astro-starlight.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 100 (no change from production)
Accessibility: 100 (no change from production)
Best Practices: 100 (no change from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions bot added 📚 docs Documentation website changes 🌟 core Changes to Starlight’s main package labels Aug 15, 2025
@astrobot-houston
Copy link
Contributor

astrobot-houston commented Aug 15, 2025

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

Locale File Note
en getting-started.mdx Source changed, localizations will be marked as outdated.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@github-actions github-actions bot added 🚨 action Changes to GitHub Action workflows 🌟 tailwind Changes to Starlight’s Tailwind package labels Aug 19, 2025
Copy link
Member

@delucis delucis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for having a go at this @trueberryless!

this issue could also be resolved in some other ways of course and I'm not sure if introducing a second IntersectionObserver is a good practice just for this edge case. Feel free to suggest cleaner solutions!

I think you might be right here — we can probably aim to fix the underlying issue if we can.

I tried adding https://github.com/pomber/intersection-observer-debugger to the page which is a handy way to visualize what is happening when working with IntersectionObserver and took a screenshot. This shows the problem quite clearly: the area we track using the rootMargin (highlighted at the top in purple) and the page title (highlighted below in green) don’t intersect:

A Starlight page. An area at the top is highlighted in purple, indicating the area being monitored by the intersection observer. Below is a green heading which is being tested for intersection. They do not intersect.

If we look at a page without the banner, we can see these areas intersect (yellow highlight), triggering the initial ToC state:

Another Starlight page. A similar rectangle at the top is highlighted in purple, but now the page title is highlighted in yellow because it falls within that rectangle.

The correct fix is probably to see if we can make the getRootMargin() method correctly detect an appropriate position by taking the banner into account:

private getRootMargin(): `-${number}px 0% ${number}px` {
const navBarHeight = document.querySelector('header')?.getBoundingClientRect().height || 0;
// `<summary>` only exists in mobile ToC, so will fall back to 0 in large viewport component.
const mobileTocHeight = this.querySelector('summary')?.getBoundingClientRect().height || 0;
/** Start intersections at nav height + 2rem padding. */
const top = navBarHeight + mobileTocHeight + 32;
/** End intersections `53px` later. This is slightly more than the maximum `margin-top` in Markdown content. */
const bottom = top + 53;
const height = document.documentElement.clientHeight;
return `-${top}px 0% ${bottom - height}px`;
}

@delucis
Copy link
Member

delucis commented Sep 15, 2025

Ah, although re-reading your second considered solution is related to that same idea. Hmm. I’ll have to think about that.

@trueberryless
Copy link
Contributor Author

Ah, although re-reading your second considered solution is related to that same idea. Hmm. I’ll have to think about that.

Yeah exactly, if we include the height of the banner in the getRootMargin() calculation, it solves this issue, but introduces a much more annoying issue - when scrolling down the page, headings that do not yet hit the top of the page are already considered "current headings" in the ToC because of the bigger root margin.

As I think that this problem would be much harder to solve with potentially more edge cases, I gave up on the idea quite early, but maybe there is potential 🤔

Also just to mention it if (maybe I forgot to say): It's impossible to change the rootMargin Fter we intitially set it, as mentioned on MDN:

The rootMargin read-only property of the IntersectionObserver interface is a string with syntax similar to that of the CSS margin property.

Notice the read-only.

@delucis
Copy link
Member

delucis commented Sep 18, 2025

It's impossible to change the rootMargin Fter we intitially set it

Yeah that’s correct, that’s why we destroy and recreate the observer on window resizes currently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚨 action Changes to GitHub Action workflows 🌟 core Changes to Starlight’s main package 📚 docs Documentation website changes 🌟 tailwind Changes to Starlight’s Tailwind package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile ToC issue with custom banners

4 participants