Support any `scopedStyleStrategy` by delucis · Pull Request #4190 · withastro/starlight · GitHub
Skip to content

Support any scopedStyleStrategy - #4190

Open
delucis wants to merge 9 commits into
mainfrom
chris/scoped-style-strategy
Open

delucis wants to merge 9 commits into
mainfrom
chris/scoped-style-strategy

Conversation

@delucis

@delucis delucis commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Background

  • Since Support Astro v3 #615 and Starlight v0.9.0, we have forced scopedStyleStrategy: "where". At the time, the higher specificity of the "attribute" default scoping strategy caused a lot of issues so we avoided it by sticking with the Astro v2 default of "where".
  • Since we released Cascade Layers #2322 the issues with specificity are much reduced: user styles will always override our @layer styles making support for different scoping strategies more viable.

This PR

  • This PR currently removes the line where we set scopedStyleStrategy and leave it to user discretion. This means by default users will switch to Astro’s default of "attribute".
  • Various small changes to styles were needed to correctly support the different scoping strategies with one case being the most common:
    • icons that had a class name passed down — these do not receive the parent scope when using the "attribute" strategy, so needed to be updated to use a parent selector and :global().
  • Because of some specificity/ordering issues within our starlight.components layer, I had to extract the <Icon> styles and move them to a global .sl-icon class name instead of just depending on an Astro-scoped svg element selector. I also added this class name to the icons generated by Markdown asides for consistency.
  • I tested all English docs pages and two Farsi docs pages with starlight-visual-diff to ensure consistency between all strategies and the existing appearance, but some more testing and thinking about the impact of this change is still very welcome.

Open questions

  • It could be less disruptive to continue setting scopedStyleStrategy for users in the cases where they do not set it themselves. It would make Starlight’s default different from Astro though, so for now I went with removing it and documenting how to restore it in the changeset.
  • Supporting multiple strategies may potentially mean there are visual differences between strategies, which we don’t notice in testing for changes made in the future. Might be worth us considering how best to handle that to ensure we maintain consistency.

Using the `"attribute"` scoping strategy (the default), Astro does not currently pass down a parent scoping attribute like it does with the `"where"` strategy’s class name. Unclear if that’s intentional on Astro’s part or not but instead we have to use a parent selector + `:global()` for the child instead.
@changeset-bot

changeset-bot Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

@netlify

netlify Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for astro-starlight ready!

Name Link
🔨 Latest commit 9b0ef2c
🔍 Latest deploy log https://app.netlify.com/projects/astro-starlight/deploys/6ab556336f54400008ed7c5e
😎 Deploy Preview https://deploy-preview-4190--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
🤖 Make changes Run an agent on this branch

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 Sep 9, 2026
@delucis delucis added this to the 0.43 milestone Sep 9, 2026
@astrobot-houston

astrobot-houston commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
/index.html 6.04 KB (-1.01% 🔽)
/guides/example/index.html 5.97 KB (-1.22% 🔽)
/_astro/*.js 25.43 KB (0%)
/_astro/*.css 14.76 KB (+0.29% 🔺)

@delucis
delucis marked this pull request as ready for review September 24, 2026 17:06
@delucis

delucis commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

Just went through this again and marked it ready for review. I think it should be a reasonably safe change thanks to @layer insulating users from our styles. And for anyone it does cause issues for, the path back to the previous behaviour is very simple.

Supporting multiple strategies may potentially mean there are visual differences between strategies, which we don’t notice in testing for changes made in the future. Might be worth us considering how best to handle that to ensure we maintain consistency.

On this point I still don’t have an answer and it may be OK? AFAICT the increase in specificity of switching to the default strategy was riskier than the decrease of using "where". At least it seems that currently switching back to "where" was still totally fine without any specific effort or care to do so.

Any approach to asserting this in tests would presumably mean running something like starlight-visual-diff in CI and that feels like a pretty big hammer for something that very often won’t apply (a small % of PRs touch styling). So maybe it’s fine to leave that as a manual thing to bear in mind.

@HiDeoo HiDeoo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the pull request 🙌

It definitely took me a while to review it as I spent a lot of time thinking about it, testing things out, etc.

Not gonna lie, this is a scary change. Not at the Starlight level (I think we can manage it, even tho the PR proves it's tricky and super easy to miss tiny issues) but more at the scale of the entire ecosystem (plugins, themes, components) and user components. Styles applied to child components using a class prop would now silently stop matching unless the child forward the data-astro-cid-* attribute (and scoped styles get a higher specificity as you mentioned). It's not something that has obvious errors, can be very subtle/easy to miss and we can't really tell people to use a tool like starlight-visual-diff on all their pages.

It could be less disruptive to continue setting scopedStyleStrategy for users in the cases where they do not set it themselves.

After initially reading the PR and starting to review it, this was one of the points that kinda reassured me about the approach and that I wanted to bring up. Altho, after thinking more about it, I don't think this is something we can do as scopedStyleStrategy defaults to attribute and we would have no way to differentiate when it was set by the user or not (unless Astro starts exposing userConfig or something like that).

I'm posting this review to share my current thoughts and the issues I spotted so far, but I'm still a bit torn on the overall change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure we can keep the current approach. Like you said, one of the most common issues you hit during the PR was icons no longer receiving the parent scope with the "attribute" strategy, but this also means that every user would also hit the same issue.

Let's say a user has the following code:

---
import { Icon } from '@astrojs/starlight/components';
---

<Icon name="star" size="10rem" class="my-icon" />

<style>
	.my-icon {
		color: red;
	}
</style>

This would no longer work and they would need to switch to :global(.my-icon) which is a worse DX imo (we would need to document it too). In this example, without a container to scope the selector, the style would also apply to any .my-icon element on the page.

I think we would need to pass the data-astro-cid-* attribute as documented:

<svg
	{...a11yAttrs}
-	class:list={['sl-icon', Astro.props.class]}
+ {...attrs}
+	class:list={['sl-icon', className]}
	style={{ '--sl-icon-color': color, '--sl-icon-size': size }}
	width="16"
	height="16"
	viewBox="0 0 24 24"
	fill="currentColor"
	set:html={Icons[name]}
/>

With this approach, I think the previous selectors would work again with all strategies, which also means we could basically revert a lot of :global() changes in the PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While browsing many pages, I manually spotted a difference in the link card icon color.

Before After
Image Image

I checked using starlight-visual-diff but this difference was not reported. After investigating, it turns out it's an issue in starlight-visual-diff with very subtle color differences. I have now updated the tool to better detect such changes, and this page (and other pages containing link cards) are now properly reported as expected.

Image

Regarding the issue, it's probably due to the .icon selectors not being updated to use :global().

Note

This would be fixed automatically with the approach suggested for the <Icon> component.

}

:global([dir='rtl']) .caret {
:global([dir='rtl']) :global(.caret) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
:global([dir='rtl']) :global(.caret) {
:global([dir='rtl']) summary :global(.caret) {

I think we need to scope this selector to avoid targeting every .caret on RTL pages.

Note

This would be fixed automatically with the approach suggested for the <Icon> component.

align-items: center;
}
.card .icon {
.card :global(.icon) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess this would style any .icon in a card, even in user content. This could even happen with a <LinkCard> in a <Card>.

Image

We probably need to use something like .title > :global(.icon).

Note

This would be fixed automatically with the approach suggested for the <Icon> component.


⚠️ **BREAKING CHANGE:** Adds support for any Astro [`scopedStyleStrategy`](https://docs.astro.build/en/reference/configuration-reference/#scopedstylestrategy) configuration.

Previously, Starlight forced the `"where"` strategy, resulting in CSS selectors like `:where(astro-HASH)`. Starlight now respects the strategy set in `astro.config.mjs` and will default to Astro’s default `"attribute"` strategy, resulting in attribute selectors like `[data-astro-cid-HASH]`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Tiny fix, also if we keep the default Astro strategy, I wonder if we should also link to https://docs.astro.build/en/guides/styling/#passing-a-class-to-a-child-component as this may be required for some of their components which is a new behavior?

This branch has not been deployed

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

Labels

🌟 core Changes to Starlight’s main package 📚 docs Documentation website changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants