A modern, lightweight client-side router for Svelte 5 with View Transition API support and advanced animation control.
ā
Client-side routing - Fast, SPA-style navigation
ā
View Transition API - Native, smooth transitions
ā
Nested routes & layouts - Organize complex apps
ā
Dynamic parameters - Extract params from URLs
ā
Wildcard routes - Catch-all for 404s
ā
Lazy loading - Code-split automatically
ā
Navigation hooks - Full lifecycle control
ā
Dual-tree fallback - Complex animations when needed
ā
Type-safe - Full TypeScript support
ā
Direction-aware - Different animations for back/forward
ā
Image loader - Progressive loading utility
ā
Comprehensive tests - 67+ passing tests for reliability
npm install @mdrv/arc
// routes.ts
import { createRouter } from '@mdrv/arc'
import About from './pages/about.svelte'
import Home from './pages/home.svelte'
export const { navigate, route, isActive } = createRouter({
routes: {
'/': Home,
'/about': About,
'/users/:id': () => import('./pages/user.svelte'),
},
})
<!-- App.svelte -->
<script>
import { generateTransitionCSS, Router } from '@mdrv/arc'
</script>
<svelte:head>
<style>
{@html generateTransitionCSS()}
</style>
</svelte:head>
<Router />
<script>
import { navigate, transitionPresets } from '@mdrv/arc'
</script>
<!-- Simple fade transition -->
<a href='/about'>About</a>
<!-- Custom transition via data attribute -->
<a href='/products' data-transition='slide'>Products</a>
<!-- Programmatic with transition -->
<button
on:click={() =>
navigate('/dashboard', {
transition: transitionPresets.material(),
})}
>
Dashboard
</button>
import { navigate, transitionPresets } from '@mdrv/arc'
// Fade transition
navigate('/about', {
transition: transitionPresets.fade(),
})
// Slide transition (auto-detects direction)
navigate('/products', {
transition: transitionPresets.slide(),
})
// Material Design transition
navigate('/dashboard', {
transition: transitionPresets.material(),
})
navigate('/special', {
transition: {
name: 'my-transition',
classes: ['custom-animation'],
onTransitionStart: (transition) => {
console.log('Transition starting...')
},
onTransitionEnd: (transition) => {
console.log('Transition complete!')
},
},
})
// Automatically detects forward/back
navigate('/next') // Slides left (forward)
navigate(-1) // Slides right (back)
For complex, programmatic animations:
navigate('/complex', {
transition: {
forceDualTree: true, // Use dual-tree instead of View Transitions
},
})
const router = createRouter({
routes: {
'/': {
transition: transitionPresets.fade(),
'/home': Home,
},
'/app': {
transition: transitionPresets.material(),
layout: AppLayout,
'/dashboard': Dashboard,
'/settings': Settings,
},
},
})
/* Your custom transition */
::view-transition-old(root) {
animation: my-fade-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: my-fade-in 0.3s ease-in;
}
@keyframes my-fade-out {
to {
opacity: 0;
transform: scale(0.9) rotate(-2deg);
}
}
@keyframes my-fade-in {
from {
opacity: 0;
transform: scale(1.1) rotate(2deg);
}
}
Then use it:
navigate('/page', {
transition: { classes: ['my-custom-transition'] },
})
For shared element transitions:
<script>
import { viewTransitionName } from '@mdrv/arc'
</script>
<!-- Image morphs between pages -->
<img
src={hero}
alt='Hero'
use:viewTransitionName='hero-image'
/>
CSS for shared element:
::view-transition-old(hero-image),
::view-transition-new(hero-image) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
When you need maximum control:
const router = createRouter({
routes: {
'/gallery': {
hooks: {
async duringLoad({ keys, transition }) {
// Preload images
await loadImages()
},
async duringRender({ keys, transition }) {
// Custom animations with GSAP, anime.js, etc.
if (!transition) { // Only if not using View Transitions
await animateGallery(keys.in, keys.out)
}
},
},
'/photos': PhotoGallery,
},
},
})
<script>
import { createRouter } from '@mdrv/arc'
const { supportsViewTransitions } = createRouter({ routes })
</script>
{#if supportsViewTransitions}
<p>š Native smooth transitions!</p>
{:else}
<p>Using dual-tree fallback</p>
{/if}
import { transitionPresets } from '@mdrv/arc'
// Available presets:
transitionPresets.fade() // Simple crossfade
transitionPresets.slide('forward') // Slide animation
transitionPresets.scale() // Scale + fade
transitionPresets.material() // Material Design style
transitionPresets.none() // No transition
transitionPresets.custom({ // Full control
name: 'my-transition',
direction: 'forward',
classes: ['my-class'],
forceDualTree: false,
onTransitionStart: (t) => {},
onTransitionEnd: (t) => {},
})
View Transition API:
Fallback:
data-preload to linkswill-change sparingly - Only for custom animationsTo use View Transitions, add the CSS:
<svelte:head>
<style>
{@html generateTransitionCSS()}
</style>
</svelte:head>
createRouter(config)New in v002:
supportsViewTransitions: booleannavigate(path, options)New options:
transition?: TransitionConfig - Transition configurationNavigateOptions.transition{
name?: string // Transition name
direction?: TransitionDirection // 'forward' | 'back' | 'replace'
classes?: string[] // CSS classes to add
forceDualTree?: boolean // Skip View Transitions
onTransitionStart?: (t) => void // Start callback
onTransitionEnd?: (t) => void // End callback
}
supportsViewTransitions() - Check API supportstartViewTransition(callback, config) - Manual transition wrapperdetectNavigationDirection(from, to, isReplace) - Direction helpertransitionPresets - Built-in transition configsgenerateTransitionCSS() - CSS helperviewTransitionName(node, name) - Svelte action for shared elementsimport { createRouter, generateTransitionCSS } from '@mdrv/arc'
const { navigate, route } = createRouter({
routes: {
'/': () => import('./pages/home.svelte'),
'/about': () => import('./pages/about.svelte'),
'/contact': () => import('./pages/contact.svelte'),
},
})
const router = createRouter({
routes: {
'/': Home,
'/products': {
transition: transitionPresets.slide(),
'/:category': ProductList,
'/:category/:id': ProductDetail,
},
'/cart': {
transition: transitionPresets.material(),
'/': Cart,
'/checkout': Checkout,
},
},
})
const router = createRouter({
routes: {
'/': Landing,
'/(auth)': {
'/login': Login,
'/register': Register,
},
'/app': {
layout: DashboardLayout,
transition: transitionPresets.fade(),
hooks: {
beforeLoad: async () => {
if (!isAuthenticated()) throw navigate('/login')
},
},
'/': Dashboard,
'/profile': Profile,
'/settings': Settings,
},
},
})
@mdrv/arc includes a comprehensive test suite with 67 passing tests covering core functionality.
# Run all tests
bun run test:run
# Watch mode
bun run test
# With coverage
bun run test:coverage
For detailed testing documentation, see docs/testing.
supportsViewTransitionsgenerateTransitionCSS()Use will-change in CSS:
[data-view-transition-name] {
will-change: transform, opacity;
}
navigate('/page', {
transition: transitionPresets.none(),
})
MIT