diff --git a/.changeset/blue-otters-shave.md b/.changeset/blue-otters-shave.md
new file mode 100644
index 0000000000..850356c458
--- /dev/null
+++ b/.changeset/blue-otters-shave.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': patch
+---
+
+Fixes a `` sync issue when inconsistently using the `icon` prop or not on `` components.
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx
index 17e643a613..c01ba8c12f 100644
--- a/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx
+++ b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx
@@ -35,3 +35,20 @@ A set of tabs using the `style` sync key.
css code
tailwind code
+
+Another set of tabs using the `pkg` sync key and using icons.
+
+
+
+ another npm command
+
+
+ another pnpm command
+
+
+ another bun command
+
+
+ another yarn command
+
+
diff --git a/packages/starlight/__e2e__/tabs.test.ts b/packages/starlight/__e2e__/tabs.test.ts
index e0a36ed536..f10f79729a 100644
--- a/packages/starlight/__e2e__/tabs.test.ts
+++ b/packages/starlight/__e2e__/tabs.test.ts
@@ -116,6 +116,29 @@ test('preserves tabs position when alternating between tabs with different conte
expect((await tabs.boundingBox())?.y).toBe(initialBoundingBox?.y);
});
+test('syncs tabs with the same sync key if they do not consistenly use icons', async ({
+ page,
+ starlight,
+}) => {
+ await starlight.goto('/tabs');
+
+ const tabs = page.locator('starlight-tabs');
+ const pkgTabsA = tabs.nth(0); // This set does not use icons for tab items.
+ const pkgTabsB = tabs.nth(4); // This set uses icons for tab items.
+
+ // Select the pnpm tab in the first set of synced tabs.
+ await pkgTabsA.getByRole('tab').filter({ hasText: 'pnpm' }).click();
+
+ await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command');
+ await expectSelectedTab(pkgTabsB, 'pnpm', 'another pnpm command');
+
+ // Select the yarn tab in the second set of synced tabs.
+ await pkgTabsB.getByRole('tab').filter({ hasText: 'yarn' }).click();
+
+ await expectSelectedTab(pkgTabsB, 'yarn', 'another yarn command');
+ await expectSelectedTab(pkgTabsA, 'yarn', 'yarn command');
+});
+
async function expectSelectedTab(tabs: Locator, label: string, panel: string) {
expect((await tabs.getByRole('tab', { selected: true }).textContent())?.trim()).toBe(label);
expect((await tabs.getByRole('tabpanel').textContent())?.trim()).toBe(panel);
diff --git a/packages/starlight/user-components/Tabs.astro b/packages/starlight/user-components/Tabs.astro
index 376fb19c93..f93475c17b 100644
--- a/packages/starlight/user-components/Tabs.astro
+++ b/packages/starlight/user-components/Tabs.astro
@@ -158,7 +158,7 @@ const { html, panels } = processPanels(panelHtml);
newTab.setAttribute('aria-selected', 'true');
if (shouldSync) {
newTab.focus();
- StarlightTabs.#syncTabs(this, newTab.textContent);
+ StarlightTabs.#syncTabs(this, newTab.innerText);
window.scrollTo({
top: window.scrollY + (this.getBoundingClientRect().top - previousTabsOffset),
});
@@ -173,7 +173,7 @@ const { html, panels } = processPanels(panelHtml);
for (const receiver of syncedTabs) {
if (receiver === emitter) continue;
- const labelIndex = receiver.tabs.findIndex((tab) => tab.textContent === label);
+ const labelIndex = receiver.tabs.findIndex((tab) => tab.innerText === label);
if (labelIndex === -1) continue;
receiver.switchTab(receiver.tabs[labelIndex], labelIndex, false);
}