diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro
index 612b89659b..cb6cbbd336 100644
--- a/packages/astro/components/ViewTransitions.astro
+++ b/packages/astro/components/ViewTransitions.astro
@@ -136,6 +136,18 @@ const { fallback = 'animate' } = Astro.props as Props;
// Remove them before swapping.
doc.querySelectorAll('head noscript').forEach((el) => el.remove());
+ // swap attributes of the html element
+ // - delete all attributes from the current document
+ // - insert all attributes from doc
+ // - reinsert all original attributes that are named 'data-astro-*'
+ const html = document.documentElement;
+ const astro = [...html.attributes].filter(
+ ({ name }) => (html.removeAttribute(name), name.startsWith('data-astro-'))
+ );
+ [...doc.documentElement.attributes, ...astro].forEach(({ name, value }) =>
+ html.setAttribute(name, value)
+ );
+
// Swap head
for (const el of Array.from(document.head.children)) {
const newEl = persistedHeadElement(el);
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/components/AttributedLayout.astro b/packages/astro/e2e/fixtures/view-transitions/src/components/AttributedLayout.astro
new file mode 100644
index 0000000000..7a3284dd4e
--- /dev/null
+++ b/packages/astro/e2e/fixtures/view-transitions/src/components/AttributedLayout.astro
@@ -0,0 +1,17 @@
+---
+import { ViewTransitions } from 'astro:transitions';
+import { HTMLAttributes } from 'astro/types';
+
+interface Props extends HTMLAttributes<'html'> {}
+---
+
+
+ Testing
+
+
+
+
+
+
+
+
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/other-attributes.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/other-attributes.astro
new file mode 100644
index 0000000000..ade923277b
--- /dev/null
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/other-attributes.astro
@@ -0,0 +1,11 @@
+---
+import AttributeLayout from '../components/AttributedLayout.astro';
+---
+
+ Page with other attributes
+
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/some-attributes.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/some-attributes.astro
new file mode 100644
index 0000000000..165ef81c11
--- /dev/null
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/some-attributes.astro
@@ -0,0 +1,7 @@
+---
+import AttributedLayout from '../components/AttributedLayout.astro';
+---
+
+ Page with some attributes
+ Other attributes
+
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index c681bfea03..57e23e6a91 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -397,3 +397,25 @@ test.describe('View Transitions', () => {
).toEqual(1);
});
});
+
+test('Navigation also swaps the attributes of the document root', async ({ page, astro }) => {
+ page.on('console', (msg) => console.log(msg.text()));
+ await page.goto(astro.resolveUrl('/some-attributes'));
+ let p = page.locator('#heading');
+ await expect(p, 'should have content').toHaveText('Page with some attributes');
+
+ let h = page.locator('html');
+ await expect(h, 'should have content').toHaveAttribute('lang', 'en');
+
+ await page.click('#click-other-attributes');
+ p = page.locator('#heading');
+ await expect(p, 'should have content').toHaveText('Page with other attributes');
+
+ h = page.locator('html');
+ await expect(h, 'should have content').toHaveAttribute('lang', 'es');
+ await expect(h, 'should have content').toHaveAttribute('style', 'background-color: green');
+ await expect(h, 'should have content').toHaveAttribute('data-other-name', 'value');
+ await expect(h, 'should have content').toHaveAttribute('data-astro-fake', 'value');
+ await expect(h, 'should have content').toHaveAttribute('data-astro-transition', 'forward');
+ await expect(h, 'should be absent').not.toHaveAttribute('class', /.*/);
+});