diff --git a/examples/kitchen-sink-vite/.gitignore b/examples/kitchen-sink-vite/.gitignore
new file mode 100644
index 000000000..364fdec1a
--- /dev/null
+++ b/examples/kitchen-sink-vite/.gitignore
@@ -0,0 +1 @@
+public/
diff --git a/examples/kitchen-sink-vite/README.md b/examples/kitchen-sink-vite/README.md
new file mode 100644
index 000000000..9eeb28b55
--- /dev/null
+++ b/examples/kitchen-sink-vite/README.md
@@ -0,0 +1,33 @@
+# Kitchen Sink
+
+This example demonstrates all the pieces of Page Kit working together. It is intended to be used as a testbed for the Page Kit team to find out how packages should be composed together and try out the interfaces between them.
+
+### Running the app
+
+First run build at the root of the app to pick up the changes and the compiling of shared packages
+
+```bash
+npm run build
+```
+
+To start the example app enter this directory:
+
+```bash
+cd examples/kitchen-sink
+```
+
+To build the example app:
+
+```bash
+npm run build
+```
+
+Then start the app by running:
+
+```bash
+npm start
+#or
+npm run dev
+```
+
+Finally open `http://localhost:3456/` in your browser.
diff --git a/examples/kitchen-sink-vite/__test__/build.test.js b/examples/kitchen-sink-vite/__test__/build.test.js
new file mode 100644
index 000000000..fc8cc776e
--- /dev/null
+++ b/examples/kitchen-sink-vite/__test__/build.test.js
@@ -0,0 +1,32 @@
+const fs = require('fs')
+
+describe('examples/kitchen-sink/build', () => {
+ it('creates the expected JS, CSS, and manifest files', () => {
+ const output = fs.readdirSync('./public')
+ expect(output).toMatchInlineSnapshot(`
+ [
+ "assets-manifest.json",
+ "async.css",
+ "financial-times-n-tracking.bundle.js",
+ "financial-times-o-footer.bundle.js",
+ "financial-times-o-grid.bundle.js",
+ "financial-times-o-header.bundle.js",
+ "financial-times-o-private-foundation.bundle.js",
+ "financial-times-o-toggle.bundle.js",
+ "financial-times-o-tracking.bundle.js",
+ "financial-times-o-utils.bundle.js",
+ "financial-times-o-viewport.bundle.js",
+ "page-kit-components.bundle.js",
+ "page-kit-layout-styles.css",
+ "preact.bundle.js",
+ "privacy-components.bundle.js",
+ "scripts.bundle.js",
+ "shared.stable.bundle.js",
+ "styles.css",
+ "vendors.bundle.js",
+ "vendors.css",
+ "webpack-runtime.bundle.js",
+ ]
+ `)
+ })
+})
diff --git a/examples/kitchen-sink-vite/__test__/integration.test.js b/examples/kitchen-sink-vite/__test__/integration.test.js
new file mode 100644
index 000000000..f7079fa41
--- /dev/null
+++ b/examples/kitchen-sink-vite/__test__/integration.test.js
@@ -0,0 +1,62 @@
+const app = require('../server/app')
+const request = require('supertest')
+
+describe('examples/kitchen-sink/integration', () => {
+ let response
+
+ beforeEach(async () => {
+ response = await request(app)
+ .get('/')
+ // NOTE: FT.com apps expect many headers to be set by the CDN+preflight and/or the router.
+ .set({
+ 'ft-edition': 'international'
+ })
+ })
+
+ it('returns an OK response', () => {
+ expect(response.statusCode).toBe(200)
+ })
+
+ it('renders the Financial Times Header Logo as a link', () => {
+ expect(response.text).toContain('data-trackable="logo" href="/"')
+ })
+
+ it('renders the header top components; search and menu', () => {
+ expect(response.text).toContain('data-trackable="search-toggle">')
+ expect(response.text).toContain('data-trackable="drawer-toggle"')
+ })
+
+ it('populates navigation elements with navigation data', () => {
+ expect(response.text).toContain('data-trackable="Home"')
+ expect(response.text).toContain('data-trackable="World"')
+ expect(response.text).toContain('data-trackable="Markets"')
+ })
+
+ it('populates drawer elements with navigation data', () => {
+ expect(response.text).toContain('o-header__drawer-menu--primary')
+ expect(response.text).toContain('o-header__drawer-menu--user')
+ })
+
+ it('populates meganav elements with navigation data', () => {
+ expect(response.text).toContain('data-trackable="meganav | World"')
+ expect(response.text).toContain('data-trackable="meganav | Markets"')
+ expect(response.text).toContain('data-trackable="meganav | Opinion"')
+ expect(response.text).toContain('o-header__mega-heading')
+ expect(response.text).toContain('o-header__mega-content')
+ expect(response.text).toContain('o-header__mega-item')
+ })
+
+ it('renders edition with current edition selected', () => {
+ expect(response.text).toContain(
+ ''
+ )
+ })
+
+ it('renders app context data as embedded JSON', () => {
+ expect(response.text).toContain('