cross-router-svelte
v1.0.4
Published
Svelte adapter for the cross-router library.
Maintainers
Readme
cross-router-svelte
Svelte 5 adapter for cross-router. Provides RouterView, Link, Form, hooks, and router factories.
Installation
The package is built on top of cross-router-core: you only need to install this package, it re-exports everything from core.
pnpm add cross-router-svelteRequires Svelte 5.
Quick start
// router.ts
import type { RouteDefinition } from 'cross-router-svelte'
import { createBrowserRouter } from 'cross-router-svelte'
import RootLayout from './layouts/RootLayout.svelte'
import HomePage from './pages/HomePage.svelte'
import NotFound from './pages/NotFound.svelte'
const routes: RouteDefinition[] = [
{
id: 'root',
path: '/',
component: RootLayout,
errorComponent: RootLayout,
children: [
{
id: 'home',
index: true,
component: HomePage,
loader: async () => ({ message: 'Hello' }),
},
{
id: 'not-found',
path: '*',
component: NotFound,
},
],
},
]
export const router = createBrowserRouter({}, routes)<!-- App.svelte -->
<script>
import { RouterView } from 'cross-router-svelte'
import { router } from './router'
</script>
<RouterView {router} />Router factories
createBrowserRouter(options, routes?)
Creates a router backed by the browser History API. The standard factory for browser apps.
import { createBrowserRouter } from 'cross-router-svelte'
const router = createBrowserRouter(
{ middleware: [authMiddleware] },
routes
)Components
<RouterView>
Mounts the router and renders the matched route tree. Place once at the root of your app.
<RouterView router={myRouter} />| Prop | Type | Description |
|---|---|---|
| router | RouterInstance | Required on the root instance. Omit on nested instances. |
Nested layouts render child routes by placing a <RouterView> inside their outlet snippet:
<!-- RootLayout.svelte -->
<script>
const { outlet } = $props()
</script>
<nav>...</nav>
<main>
{@render outlet?.()}
</main><Link>
Client-side navigation link. Intercepts clicks and delegates to the router, respecting modifier keys and target="_blank".
<Link href='/dashboard'>Dashboard</Link>| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | — | Target path. Required. |
| replace | boolean | false | Replace the current history entry instead of pushing. |
| viewTransition | boolean | false | Enable the View Transition API for this navigation. |
| state | unknown | — | History state to attach to the navigation entry. |
| activeClass | string | 'active' | Class applied when href matches the start of the current path. |
| exactActiveClass | string | 'exact-active' | Class applied when href matches the current path exactly. |
| class | string | — | Additional CSS classes. |
| target | string | — | Forwarded to the underlying <a> element. |
| rel | string | — | Forwarded to the underlying <a> element. |
<Link> also sets aria-current="page" automatically when the link matches exactly.
<Form>
Submits form data to a route action via the router instead of the browser.
<Form action='/profile'>
<input name='username' />
<button type='submit'>Save</button>
</Form>| Prop | Type | Description |
|---|---|---|
| action | string | Route path that handles the submission. Defaults to the current pathname. |
| class | string | CSS class on the <form> element. |
| id | string | HTML id on the <form> element. |
| enctype | string | Forwarded to the <form> element. |
| onBeforeSubmit | (formData) => boolean \| Promise<boolean> | Called before submission. Return false to cancel. |
| onAfterSubmit | () => void | Called after submission completes. |
Hooks
All hooks must be called during component initialization — the same constraint as Svelte's getContext. They return reactive getter functions, not raw values.
<script>
import { useLoaderData } from 'cross-router-svelte'
const getData = useLoaderData<{ message: string }>()
</script>
<p>{getData().message}</p>useLoaderData<T>()
Returns the loader data for the current route.
<script>
import { loader } from './my-route'
// Type the action data directly
const getData = useActionData<{ user: User }>()
// Or pass the loader function type (recommended)
const getData = useActionData<typeof loader>()
</script>
<h1>Hello {getData().user.name}</h1>useParams<T>()
Returns the URL params for the current route.
<script>
const getParams = useParams<{ id: string }>()
</script>
<p>User ID: {getParams().id}</p>useActionData<T>()
Returns the data returned by the last action, or undefined if no action has run.
<script>
import { action } from './my-route'
// Type the action data directly
const getActionData = useActionData<{ error?: string }>()
// Or pass the action function type (recommended)
const getActionData = useActionData<typeof action>()
</script>
{#if getActionData()?.error}
<p class='error'>{getActionData().error}</p>
{/if}useNavigate()
Returns a function for programmatic navigation.
<script>
import { useNavigate } from 'cross-router-svelte'
const navigate = useNavigate()
</script>
<button onclick={() => navigate('/dashboard')}>Go to dashboard</button>
<button onclick={() => navigate('/dashboard', { replace: true })}>Replace</button>useLocation()
Returns the current Location object.
<script>
const getLocation = useLocation()
</script>
<p>Current path: {getLocation().pathname}</p>useMatch()
Returns the RouteMatch for the current route depth. Must be called inside a route component rendered by <RouterView>.
useMatches()
Returns all RouteMatch objects from root to the current leaf — useful for breadcrumbs.
<script>
const getMatches = useMatches()
</script>
<nav>
{#each getMatches() as match}
<Link href={match.pathname}>{match.route.id}</Link>
{/each}
</nav>useNavigationStatus()
Returns the current navigation status: 'idle', 'loading', 'submitting', 'redirecting', or 'error'.
<script>
const getStatus = useNavigationStatus()
</script>
{#if getStatus() === 'loading'}
<LoadingBar />
{/if}useIsTransitioning()
Returns true while a navigation is in progress.
useRouterState()
Returns the full NavigationState object. Prefer the more specific hooks above when possible.
useSubmit()
Returns a function to submit form data programmatically.
<script>
const submit = useSubmit()
function handleDelete() {
const formData = new FormData()
formData.set('id', '123')
submit(formData, { action: '/items' })
}
</script>Accessing the router outside components
Use getRouter() to access the router instance anywhere — stores, event handlers, plugin code — after <RouterView> has mounted.
import { getRouter } from 'cross-router-svelte'
export function logout() {
clearSession()
getRouter().navigate('/login')
}Runtime route patching
Add and remove routes from the live router at any time via getRouter().
import { getRouter } from 'cross-router-svelte'
import PluginPage from './PluginPage.svelte'
// Install a plugin's routes
getRouter().patch([
{ id: 'plugin', path: 'plugin', component: PluginPage }
], 'root')
// Remove them when the plugin unloads
getRouter().unpatch('plugin')Cross-framework rendering
Use the routes() helper to tag route definitions as Svelte-rendered. This is optional for pure Svelte apps but required when mixing frameworks — it marks ownership so RouterView knows which renderer to use for each route.
import { routes as solidRoutes } from 'cross-router-solid'
import { routes } from 'cross-router-svelte'
const appRoutes = [
{
id: 'root',
path: '/',
component: RootLayout,
children: [
...routes([
{ id: 'home', index: true, component: HomePage },
]),
...solidRoutes([
{ id: 'widget', path: 'widget', component: SolidWidget },
]),
{ id: 'not-found', path: '*', component: NotFound },
],
},
]Debug logging
Enable verbose logging in the browser console to trace navigations, matched routes, middleware, loaders, and registry patches.
import { disableDebug, enableDebug } from 'cross-router-svelte'
enableDebug() // all scopes
enableDebug('router,matcher') // specific scopes: router, matcher, registry, loader, middleware
disableDebug()Logging persists across reloads via localStorage. Reload the page after enabling to see logs from initialisation.
License
This project is under MIT license.
