diff --git a/.eslintignore b/.eslintignore
index e061d4c1b47..ce46a7f8c51 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -8,6 +8,7 @@ pnpm-lock.yaml
/packages/toolpad-studio/public
build
+dist
/examples/*/toolpad.yml
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1974a40fe06..3766b6eca27 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -71,7 +71,11 @@ You can also test the example apps to make sure they work as expected.
pnpm --filter @toolpad/core dev
```
-3. Run the example app
+3. Install then run the example app
+
+ ```bash
+ pnpm --filter core-nextjs install
+ ```
```bash
pnpm --filter core-nextjs dev
diff --git a/docs/data/toolpad/core/introduction/ReactRouter.js b/docs/data/toolpad/core/introduction/ReactRouter.js
new file mode 100644
index 00000000000..7b8059c1283
--- /dev/null
+++ b/docs/data/toolpad/core/introduction/ReactRouter.js
@@ -0,0 +1,130 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { createTheme } from '@mui/material/styles';
+import Typography from '@mui/material/Typography';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { createMemoryRouter, RouterProvider, Outlet } from 'react-router-dom';
+import { AppProvider } from '@toolpad/core/react-router-dom';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+function Layout() {
+ return (
+
+
+
+
+
+ );
+}
+
+function DashboardPage() {
+ return Welcome to Toolpad!;
+}
+
+function OrdersPage() {
+ return Welcome to the orders page!;
+}
+
+const NAVIGATION = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+const demoTheme = createTheme({
+ cssVariables: {
+ colorSchemeSelector: 'data-toolpad-color-scheme',
+ },
+ colorSchemes: { light: true, dark: true },
+ breakpoints: {
+ values: {
+ xs: 0,
+ sm: 600,
+ md: 600,
+ lg: 1200,
+ xl: 1536,
+ },
+ },
+});
+
+function App(props) {
+ const { window } = props;
+
+ return (
+
+
+
+ );
+}
+
+App.propTypes = {
+ window: PropTypes.object,
+};
+
+function ReactRouter(props) {
+ const { window } = props;
+
+ // Remove this const when copying and pasting into your project.
+ const demoWindow = window !== undefined ? window() : undefined;
+
+ // preview-start
+ const router = React.useMemo(
+ () =>
+ createMemoryRouter([
+ {
+ element: , // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+ ]),
+ [demoWindow],
+ );
+
+ return ;
+ // preview-end
+}
+
+ReactRouter.propTypes = {
+ /**
+ * Injected by the documentation to work in an iframe.
+ * Remove this when copying and pasting into your project.
+ */
+ window: PropTypes.func.isRequired,
+};
+
+export default ReactRouter;
diff --git a/docs/data/toolpad/core/introduction/ReactRouter.tsx b/docs/data/toolpad/core/introduction/ReactRouter.tsx
new file mode 100644
index 00000000000..606eea7f552
--- /dev/null
+++ b/docs/data/toolpad/core/introduction/ReactRouter.tsx
@@ -0,0 +1,124 @@
+import * as React from 'react';
+import { createTheme } from '@mui/material/styles';
+import Typography from '@mui/material/Typography';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { createMemoryRouter, RouterProvider, Outlet } from 'react-router-dom';
+import { AppProvider } from '@toolpad/core/react-router-dom';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import type { Navigation } from '@toolpad/core';
+
+function Layout() {
+ return (
+
+
+
+
+
+ );
+}
+
+function DashboardPage() {
+ return Welcome to Toolpad!;
+}
+
+function OrdersPage() {
+ return Welcome to the orders page!;
+}
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+const demoTheme = createTheme({
+ cssVariables: {
+ colorSchemeSelector: 'data-toolpad-color-scheme',
+ },
+ colorSchemes: { light: true, dark: true },
+ breakpoints: {
+ values: {
+ xs: 0,
+ sm: 600,
+ md: 600,
+ lg: 1200,
+ xl: 1536,
+ },
+ },
+});
+
+function App(props: { window?: Window }) {
+ const { window } = props;
+
+ return (
+
+
+
+ );
+}
+
+interface DemoProps {
+ /**
+ * Injected by the documentation to work in an iframe.
+ * Remove this when copying and pasting into your project.
+ */
+ window: () => Window;
+}
+
+export default function ReactRouter(props: DemoProps) {
+ const { window } = props;
+
+ // Remove this const when copying and pasting into your project.
+ const demoWindow = window !== undefined ? window() : undefined;
+
+ // preview-start
+ const router = React.useMemo(
+ () =>
+ createMemoryRouter([
+ {
+ element: , // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+ ]),
+ [demoWindow],
+ );
+
+ return ;
+ // preview-end
+}
diff --git a/docs/data/toolpad/core/introduction/ReactRouter.tsx.preview b/docs/data/toolpad/core/introduction/ReactRouter.tsx.preview
new file mode 100644
index 00000000000..3fd158b2d3e
--- /dev/null
+++ b/docs/data/toolpad/core/introduction/ReactRouter.tsx.preview
@@ -0,0 +1,27 @@
+const router = React.useMemo(
+ () =>
+ createMemoryRouter([
+ {
+ element: , // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+ ]),
+ [demoWindow],
+);
+
+return ;
\ No newline at end of file
diff --git a/docs/data/toolpad/core/introduction/base-concepts.md b/docs/data/toolpad/core/introduction/base-concepts.md
index 72e52f80e16..96409572c4a 100644
--- a/docs/data/toolpad/core/introduction/base-concepts.md
+++ b/docs/data/toolpad/core/introduction/base-concepts.md
@@ -69,7 +69,7 @@ You can pass the router implementation to the `AppProvider` component using the
:::success
If you are using Next.js, use the `AppProvider` exported from `@toolpad/core/nextjs`.
-If you are building a single-page application with React Router for routing, use the `AppProvider` exported from `@toolpad/core/react-router-dom`.
+If you are building a single-page application (with [Vite](https://vite.dev/), for example) using React Router for routing, use the `AppProvider` exported from `@toolpad/core/react-router-dom`.
This automatically sets up the router for you, so that you don't need to pass the `router` prop.
:::
diff --git a/docs/data/toolpad/core/introduction/integration.md b/docs/data/toolpad/core/introduction/integration.md
index fda56d5534b..b8e21e4f315 100644
--- a/docs/data/toolpad/core/introduction/integration.md
+++ b/docs/data/toolpad/core/introduction/integration.md
@@ -666,3 +666,175 @@ That's it! You now have Toolpad Core integrated into your Next.js Pages Router a
:::info
For a full working example with authentication included, see the [Toolpad Core Next.js Pages app with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core-auth-nextjs-pages)
:::
+
+## React Router
+
+To integrate Toolpad Core into a single-page app (with [Vite](https://vite.dev/), for example) using **React Router**, follow these steps:
+
+### 1. Wrap all your pages in an `AppProvider`
+
+In your router configuration (e.g.: `src/main.tsx`), use a shared component or element (e.g.: `src/App.tsx`) as a root **layout route** that will wrap the whole application with the `AppProvider` from `@toolpad/core/react-router-dom`.
+
+You must use the `` component from `react-router-dom` in this root layout element or component.
+
+```tsx title="src/main.tsx"
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router-dom';
+import App from './App';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+
+const router = createBrowserRouter([
+ {
+ Component: App, // root layout route
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
+```
+
+```tsx title="src/App.tsx"
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { AppProvider } from '@toolpad/core/react-router-dom';
+import { Outlet } from 'react-router-dom';
+import type { Navigation } from '@toolpad/core';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+```
+
+### 2. Create a dashboard layout
+
+Create a layout file for your dashboard pages (e.g.: `src/layouts/dashboard.tsx`), to also be used as a layout route with the `` component from `react-router-dom`:
+
+```tsx title="src/layouts/dashboard.tsx"
+import * as React from 'react';
+import { Outlet } from 'react-router-dom';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default function Layout() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation.
+
+You can then add this layout component to your React Router configuration (e.g.: `src/main.tsx`), as a child of the root layout route created in step 1.
+
+```tsx title="src/main.tsx"
+import Layout from './layouts/dashboard';
+
+//...
+const router = createBrowserRouter([
+ {
+ Component: App, // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ },
+ ],
+ },
+]);
+//...
+```
+
+### 3. Create pages
+
+Create a dashboard page (e.g.: `src/pages/index.tsx`) and an orders page (`src/pages/orders.tsx`).
+
+```tsx title="src/pages/index.tsx"
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function DashboardPage() {
+ return Welcome to Toolpad!;
+}
+```
+
+```tsx title="src/pages/orders.tsx"
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the Toolpad orders!;
+}
+```
+
+You can then add these page components as routes to your React Router configuration (e.g.: `src/main.tsx`). By adding them as children of the layout route created in step 2, they will automatically be wrapped with that dashboard layout:
+
+```tsx title="src/main.tsx"
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+
+//...
+const router = createBrowserRouter([
+ {
+ Component: App, // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+]);
+//...
+```
+
+That's it! You now have Toolpad Core integrated into your single-page app with React Router!
+
+{{"demo": "ReactRouter.js", "height": 500, "iframe": true, "hideToolbar": true}}
+
+:::info
+For a full working example, see the [Toolpad Core Vite app with React Router example](https://github.com/mui/toolpad/tree/master/examples/core-vite)
+:::
diff --git a/docs/public/static/toolpad/docs/core/vite-react-router.png b/docs/public/static/toolpad/docs/core/vite-react-router.png
new file mode 100644
index 00000000000..d7e6fa2935e
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/vite-react-router.png differ
diff --git a/docs/src/modules/components/ExamplesGrid/core-examples.ts b/docs/src/modules/components/ExamplesGrid/core-examples.ts
index d1eec5beb56..b99860b01a8 100644
--- a/docs/src/modules/components/ExamplesGrid/core-examples.ts
+++ b/docs/src/modules/components/ExamplesGrid/core-examples.ts
@@ -22,5 +22,12 @@ export default function examples() {
src: '/static/toolpad/docs/core/auth-next.png',
source: 'https://github.com/mui/toolpad/tree/master/examples/core-auth-nextjs-pages',
},
+ {
+ title: 'Vite with React Router',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Vite and React Router',
+ src: '/static/toolpad/docs/core/vite-react-router.png',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core-vite',
+ },
];
}
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/README.md b/examples/core-auth-nextjs-pages-nextauth-4/README.md
index 713d86babaf..d33f0dc405c 100644
--- a/examples/core-auth-nextjs-pages-nextauth-4/README.md
+++ b/examples/core-auth-nextjs-pages-nextauth-4/README.md
@@ -1,4 +1,4 @@
-# Toolpad Core Playground - Next.js Pages Router with Next Auth 4
+# Toolpad Core - Next.js Pages Router with Next Auth 4
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
diff --git a/examples/core-auth-nextjs-pages/README.md b/examples/core-auth-nextjs-pages/README.md
index 18729facd52..e92d004f1c3 100644
--- a/examples/core-auth-nextjs-pages/README.md
+++ b/examples/core-auth-nextjs-pages/README.md
@@ -1,4 +1,4 @@
-# Toolpad Core Playground - Next.js Pages Router
+# Toolpad Core - Next.js Pages Router
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
diff --git a/examples/core-auth-nextjs/README.md b/examples/core-auth-nextjs/README.md
index 38b913c6863..be121f349a6 100644
--- a/examples/core-auth-nextjs/README.md
+++ b/examples/core-auth-nextjs/README.md
@@ -1,4 +1,4 @@
-# Toolpad Core Playground - Next.js App Router
+# Toolpad Core - Next.js App Router
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
diff --git a/examples/core-vite/.gitignore b/examples/core-vite/.gitignore
new file mode 100644
index 00000000000..a547bf36d8d
--- /dev/null
+++ b/examples/core-vite/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/examples/core-vite/README.md b/examples/core-vite/README.md
new file mode 100644
index 00000000000..fdcc372ab1e
--- /dev/null
+++ b/examples/core-vite/README.md
@@ -0,0 +1,23 @@
+# Toolpad Core - Vite & React Router
+
+This example provides a minimal setup to get Toolpad Core working in Vite with HMR and some ESLint rules, as well as routing with React Router.
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
+
+## The source
+
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/core-vite)
diff --git a/examples/core-vite/index.html b/examples/core-vite/index.html
new file mode 100644
index 00000000000..1f790b03947
--- /dev/null
+++ b/examples/core-vite/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+ Toolpad Core Vite
+
+
+
+
+
+
diff --git a/examples/core-vite/package.json b/examples/core-vite/package.json
new file mode 100644
index 00000000000..afe44cc4cf7
--- /dev/null
+++ b/examples/core-vite/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "core-vite",
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11",
+ "@emotion/styled": "^11",
+ "@mui/icons-material": "^6",
+ "@mui/material": "^6",
+ "@toolpad/core": "latest",
+ "react": "^18",
+ "react-dom": "^18",
+ "react-router-dom": "^6"
+ },
+ "devDependencies": {
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@vitejs/plugin-react": "^4.3.2",
+ "typescript": "^5",
+ "vite": "^5.4.8"
+ }
+}
diff --git a/examples/core-vite/public/vite.svg b/examples/core-vite/public/vite.svg
new file mode 100644
index 00000000000..e7b8dfb1b2a
--- /dev/null
+++ b/examples/core-vite/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/core-vite/src/App.tsx b/examples/core-vite/src/App.tsx
new file mode 100644
index 00000000000..254413daced
--- /dev/null
+++ b/examples/core-vite/src/App.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { Outlet } from 'react-router-dom';
+import { AppProvider } from '@toolpad/core/react-router-dom';
+import type { Navigation } from '@toolpad/core';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/examples/core-vite/src/assets/.gitkeep b/examples/core-vite/src/assets/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/examples/core-vite/src/layouts/dashboard.tsx b/examples/core-vite/src/layouts/dashboard.tsx
new file mode 100644
index 00000000000..c540feb6e0f
--- /dev/null
+++ b/examples/core-vite/src/layouts/dashboard.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import { Outlet } from 'react-router-dom';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default function Layout() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/examples/core-vite/src/main.tsx b/examples/core-vite/src/main.tsx
new file mode 100644
index 00000000000..cda00abd9f2
--- /dev/null
+++ b/examples/core-vite/src/main.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router-dom';
+import App from './App';
+import Layout from './layouts/dashboard';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+
+const router = createBrowserRouter([
+ {
+ Component: App,
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/examples/core-vite/src/pages/index.tsx b/examples/core-vite/src/pages/index.tsx
new file mode 100644
index 00000000000..e4581fc26bf
--- /dev/null
+++ b/examples/core-vite/src/pages/index.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function DashboardPage() {
+ return Welcome to Toolpad!;
+}
diff --git a/examples/core-vite/src/pages/orders.tsx b/examples/core-vite/src/pages/orders.tsx
new file mode 100644
index 00000000000..de4948afd88
--- /dev/null
+++ b/examples/core-vite/src/pages/orders.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the Toolpad orders!;
+}
diff --git a/examples/core-vite/src/vite-env.d.ts b/examples/core-vite/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/examples/core-vite/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/core-vite/tsconfig.json b/examples/core-vite/tsconfig.json
new file mode 100644
index 00000000000..3d0a51a86e2
--- /dev/null
+++ b/examples/core-vite/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/examples/core-vite/tsconfig.node.json b/examples/core-vite/tsconfig.node.json
new file mode 100644
index 00000000000..9d31e2aed93
--- /dev/null
+++ b/examples/core-vite/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/examples/core-vite/vite.config.ts b/examples/core-vite/vite.config.ts
new file mode 100644
index 00000000000..627a3196243
--- /dev/null
+++ b/examples/core-vite/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/package.json b/package.json
index 407eb30a898..5fa3ec6a490 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"markdownlint": "markdownlint-cli2 \"**/*.md\"",
"prettier": "pretty-quick --ignore-path .eslintignore",
"prettier:all": "prettier --write . --ignore-path .eslintignore",
- "dev": "dotenv cross-env FORCE_COLOR=1 lerna -- run dev --stream --parallel --ignore docs --ignore playground-nextjs --ignore playground-nextjs-pages",
+ "dev": "dotenv cross-env FORCE_COLOR=1 lerna -- run dev --stream --parallel --ignore docs --ignore playground-nextjs --ignore playground-nextjs-pages --ignore playground-vite",
"docs:dev": "pnpm --filter docs dev",
"docs:build": "pnpm --filter docs build",
"docs:build:api:core": "tsx --tsconfig ./scripts/tsconfig.json ./scripts/docs/buildCoreApiDocs/index.ts",
diff --git a/playground/vite/.gitignore b/playground/vite/.gitignore
new file mode 100644
index 00000000000..a547bf36d8d
--- /dev/null
+++ b/playground/vite/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/playground/vite/README.md b/playground/vite/README.md
new file mode 100644
index 00000000000..77643b462dd
--- /dev/null
+++ b/playground/vite/README.md
@@ -0,0 +1,8 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
diff --git a/playground/vite/index.html b/playground/vite/index.html
new file mode 100644
index 00000000000..1f790b03947
--- /dev/null
+++ b/playground/vite/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+ Toolpad Core Vite
+
+
+
+
+
+
diff --git a/playground/vite/package.json b/playground/vite/package.json
new file mode 100644
index 00000000000..449b8b3c90e
--- /dev/null
+++ b/playground/vite/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "playground-vite",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@emotion/react": "11.13.3",
+ "@emotion/styled": "11.13.0",
+ "@mui/icons-material": "6.1.2",
+ "@mui/material": "6.1.2",
+ "@toolpad/core": "workspace:*",
+ "@types/react": "18.3.11",
+ "@types/react-dom": "18.3.0",
+ "@vitejs/plugin-react": "4.3.2",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "react-router-dom": "6.26.2",
+ "vite": "5.4.8"
+ }
+}
diff --git a/playground/vite/public/vite.svg b/playground/vite/public/vite.svg
new file mode 100644
index 00000000000..e7b8dfb1b2a
--- /dev/null
+++ b/playground/vite/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/vite/src/App.tsx b/playground/vite/src/App.tsx
new file mode 100644
index 00000000000..254413daced
--- /dev/null
+++ b/playground/vite/src/App.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { Outlet } from 'react-router-dom';
+import { AppProvider } from '@toolpad/core/react-router-dom';
+import type { Navigation } from '@toolpad/core';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/playground/vite/src/assets/.gitkeep b/playground/vite/src/assets/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/playground/vite/src/layouts/dashboard.tsx b/playground/vite/src/layouts/dashboard.tsx
new file mode 100644
index 00000000000..c540feb6e0f
--- /dev/null
+++ b/playground/vite/src/layouts/dashboard.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import { Outlet } from 'react-router-dom';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default function Layout() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/playground/vite/src/main.tsx b/playground/vite/src/main.tsx
new file mode 100644
index 00000000000..cda00abd9f2
--- /dev/null
+++ b/playground/vite/src/main.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router-dom';
+import App from './App';
+import Layout from './layouts/dashboard';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+
+const router = createBrowserRouter([
+ {
+ Component: App,
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/playground/vite/src/pages/index.tsx b/playground/vite/src/pages/index.tsx
new file mode 100644
index 00000000000..e4581fc26bf
--- /dev/null
+++ b/playground/vite/src/pages/index.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function DashboardPage() {
+ return Welcome to Toolpad!;
+}
diff --git a/playground/vite/src/pages/orders.tsx b/playground/vite/src/pages/orders.tsx
new file mode 100644
index 00000000000..de4948afd88
--- /dev/null
+++ b/playground/vite/src/pages/orders.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the Toolpad orders!;
+}
diff --git a/playground/vite/src/vite-env.d.ts b/playground/vite/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/playground/vite/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/playground/vite/tsconfig.json b/playground/vite/tsconfig.json
new file mode 100644
index 00000000000..251a83f8a97
--- /dev/null
+++ b/playground/vite/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/playground/vite/vite.config.ts b/playground/vite/vite.config.ts
new file mode 100644
index 00000000000..627a3196243
--- /dev/null
+++ b/playground/vite/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8b9772e8ac8..bdaed45ae50 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1289,6 +1289,45 @@ importers:
specifier: 18.3.1
version: 18.3.1(react@18.3.1)
+ playground/vite:
+ devDependencies:
+ '@emotion/react':
+ specifier: 11.13.3
+ version: 11.13.3(@types/react@18.3.11)(react@18.3.1)
+ '@emotion/styled':
+ specifier: 11.13.0
+ version: 11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)
+ '@mui/icons-material':
+ specifier: 6.1.2
+ version: 6.1.2(@mui/material@6.1.2(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)
+ '@mui/material':
+ specifier: 6.1.2
+ version: 6.1.2(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@toolpad/core':
+ specifier: workspace:*
+ version: link:../../packages/toolpad-core/build
+ '@types/react':
+ specifier: 18.3.11
+ version: 18.3.11
+ '@types/react-dom':
+ specifier: 18.3.0
+ version: 18.3.0
+ '@vitejs/plugin-react':
+ specifier: 4.3.2
+ version: 4.3.2(vite@5.4.8(@types/node@20.16.11)(terser@5.34.1))
+ react:
+ specifier: 18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: 18.3.1
+ version: 18.3.1(react@18.3.1)
+ react-router-dom:
+ specifier: 6.26.2
+ version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ vite:
+ specifier: 5.4.8
+ version: 5.4.8(@types/node@20.16.11)(terser@5.34.1)
+
test:
devDependencies:
'@mui/material':
@@ -2929,6 +2968,17 @@ packages:
'@types/react':
optional: true
+ '@mui/icons-material@6.1.2':
+ resolution: {integrity: sha512-7NNcjW5JoT9jHagrVbARA1o41vQY2xezDamtke+mEKKZmsJyejfRBOacSrPDfjZQ//lyhIjNKyzAwisxYJR47w==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ '@mui/material': ^6.1.2
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@mui/icons-material@6.1.3':
resolution: {integrity: sha512-QBQCCIMSAv6IkArTg4Hg8q2sJRhHOci8oPAlkHWFlt2ghBdy3EqyLbIELLE/bhpqhX+E/ZkPYGIUQCd5/L0owA==}
engines: {node: '>=14.0.0'}
@@ -3010,6 +3060,26 @@ packages:
'@types/react':
optional: true
+ '@mui/material@6.1.2':
+ resolution: {integrity: sha512-5TtHeAVX9D5d2LYfB1GAUn29BcVETVsrQ76Dwb2SpAfQGW3JVy4deJCAd0RrIkI3eEUrsl0E4xuBdreszxdTTg==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.5.0
+ '@emotion/styled': ^11.3.0
+ '@mui/material-pigment-css': ^6.1.2
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ '@mui/material-pigment-css':
+ optional: true
+ '@types/react':
+ optional: true
+
'@mui/material@6.1.3':
resolution: {integrity: sha512-loV5MBoMKLrK80JeWINmQ1A4eWoLv51O2dBPLJ260IAhupkB3Wol8lEQTEvvR2vO3o6xRHuXe1WaQEP6N3riqg==}
engines: {node: '>=14.0.0'}
@@ -4226,6 +4296,9 @@ packages:
'@types/react-dev-utils@9.0.15':
resolution: {integrity: sha512-JSZZtC8f5FD3FvlT8F6dPojPH6u0f+E/+g9DqXgQo/7GvCdPYiK+Q+mMajZClqH/f5SjyYop7v6uiLyfgnyLQA==}
+ '@types/react-dom@18.3.0':
+ resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
+
'@types/react-dom@18.3.1':
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
@@ -12177,6 +12250,14 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.11
+ '@mui/icons-material@6.1.2(@mui/material@6.1.2(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.25.7
+ '@mui/material': 6.1.2(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.11
+
'@mui/icons-material@6.1.3(@mui/material@6.1.3(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.7
@@ -12262,6 +12343,27 @@ snapshots:
'@emotion/server': 11.11.0
'@types/react': 18.3.11
+ '@mui/material@6.1.2(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.25.7
+ '@mui/core-downloads-tracker': 6.1.3
+ '@mui/system': 6.1.3(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)
+ '@mui/types': 7.2.18(@types/react@18.3.11)
+ '@mui/utils': 6.1.3(@types/react@18.3.11)(react@18.3.1)
+ '@popperjs/core': 2.11.8
+ '@types/react-transition-group': 4.4.11
+ clsx: 2.1.1
+ csstype: 3.1.3
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-is: 18.3.1
+ react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ optionalDependencies:
+ '@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1)
+ '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)
+ '@types/react': 18.3.11
+
'@mui/material@6.1.3(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.7
@@ -13595,6 +13697,10 @@ snapshots:
transitivePeerDependencies:
- debug
+ '@types/react-dom@18.3.0':
+ dependencies:
+ '@types/react': 18.3.11
+
'@types/react-dom@18.3.1':
dependencies:
'@types/react': 18.3.11