This page tracks the two most recent releases. For v5.2.0 (reactive permissions, revalidateCurrentRoute(), defineRoutes(), error-toast removal), and the full history including v5.1.x and v5.0.x, see CHANGELOG.md.

v5.3.0

Generated navigation from a route tree: a permission-aware helpers/nav-tree module, subtree-aware use:active highlighting, and distinct styling for product-level "coming soon" placeholders. One route tree can now drive your routes, your sidebar, and your navbar — with permissions applied — from a single source of truth.

subtree: true on use:active — sidebar parent/child highlighting from one action

Registers both an exact-href match and a /href/* descendants pattern from a single action call, derived from the link's href. Sidebar parents stay highlighted on their own index page and every nested URL without writing a regex or stacking two use:active calls by hand. Pair with the new subtreeClassName to style "parent of an active child" differently from "really active".

One use:active call for parent + nested highlight
 

helpers/nav-tree — permission-aware filtering for tree-shaped menus

New module exported at @keenmate/svelte-spa-router/helpers/nav-tree. Walks a route tree, runs hasPermission() per node, and either drops inaccessible nodes (mode: 'hide') or marks them with _forbidden: true for styled disabled rendering (mode: 'disable'). Ancestor permissions enforced via sequential checks (one per level), not by merging spec objects — mirrors the router's hierarchical-mode pipeline. Plus a hidden: boolean | (node) => boolean getter that's always-destructive in both modes — read a $state rune or a feature flag inside the getter and the whole filter result becomes reactive.

One tree drives routes + nav, with permissions
 

disabled placeholders + distinct FilterOptions.disabledClassName

A disabled: true node is a product-level placeholder signal ("coming soon", "in private beta"), not a user-permission concern — so it renders as forbidden in both filter modes rather than being dropped like a permission failure (ancestor permission denial still hides disabled descendants — you can't see a placeholder in a section you can't enter). The new disabledClassName option then lets you style those placeholders distinctly from permission-denied items even though both render through the same forbidden branch. When a node is both disabled AND permission-denied (rare), disabledClassName wins — the product-level signal is the more permanent one. Cascade parents (forbidden only because every visible child is) keep forbiddenClassName. Default undefined → falls back to forbiddenClassName; fully backward compatible.

Distinct styling for disabled vs forbidden
 

Stacked use:active actions cooperate cleanly

Two use:active calls on one element used to fight each other: the last invocation unconditionally toggled its own result, so use:active use:active={'/foo/*'} silently broke on the bare path (the prefix action stripped the class the default action just added). Replaced the internal toggleClasses with syncClassesForNode that aggregates across every entry — an active class is present iff any entry's pattern matches. The subtree option above rides on this aggregation model.

/nav-tree-demo showcase + <NavLink> reference

The example app's tree-driven demo (/nav-tree-demo) walks a permission-filtered tree to render its sidebar AND its navbar AND its routes — one source of truth drives all three. Three live toggles in the sidebar — user, hide/disable mode, and a feature-flag rune — let you see every filter behavior change in place with no page reload. The <NavLink> wrapper around use:link + use:active is a copy-paste reference for handling subtree and _forbidden. The topbar items also opt into the Floating UI RichTooltip wrapper (via the meta.docsUrl convention) so one richTooltipContent snippet powers both the horizontal navbar and the vertical sidebar. Covered end-to-end by Playwright tests.

AI-facing docs refreshed

The previous ai/link-actions.txt incorrectly claimed <a href="/docs/*" use:active> matched bare /docs. It doesn't: regexparam@2.0.2 compiles /docs/* to a pattern requiring the slash after "docs", so bare /docs falls through. Rewritten as "descendants only" with the quirk explained, plus new sections covering branch matching (subtree vs regex vs stacked actions), sidebar-with-submenu, two-class parent/child pattern, generating nav from a route tree, and filtering by permissions. Same expansion propagated to this site's Link actions feature page — including a Reactivity section explaining the $derived(filterByPermissions(...)) chain.

v5.2.1

Bare-function routes unblocked on Svelte 5.5+ / Vite 7 / plugin-svelte 6

Single-issue release. Router.svelte's component validator threw Invalid component object for every bare-function route ({ '/': Foo }) on newer toolchains — the Svelte 5 compiler dropped a set of parens in a nested boolean guard, flipping the check. wrap()-ed routes accidentally dodged it. The fix hoists the type checks into named locals (isComponentFn / isWrappedRoute) so the guard is single-nesting and immune to the compiler's paren elision. If you were workarounded with wrap({ component: Foo }) you can now drop it. Either way: just upgrade.

Upgrading from older versions

v5.2.x → v5.3.0 requires no code changes. Stacked use:active cooperation is backward compatible for the single-action-per-node case (the overwhelmingly common case). Pick up subtree: true and helpers/nav-tree when you're ready. (If you happened to try a 5.3.0-rc01 pre-release, rename isHidden:hidden: in your nav-tree definitions — see the Migration Guide.)

v5.0 / v5.1 → v5.2.x introduced a breaking removal in v5.2.0: the built-in error toast (showToast) is gone from GlobalErrorHandler — wire your own toast library inside the onError callback. See the Migration Guide for the details and the major v4 → v5 breakages.

Install

 

Then jump to the Getting Started guide.

Feedback