Cascading page title manager for SvelteKit. It builds titles like "Page • Section • App" automatically as your layouts and pages render.
npm install svelte-titleFirst, set up your root layout:
<!-- src/routes/+layout.svelte -->
<script>
import { page } from '$app/state'
import { Title, resetLevelCounter } from 'svelte-title'
$effect(() => {
page.url.pathname
resetLevelCounter()
})
</script>
<Title title="App" />Call resetLevelCounter() when the URL changes so new titles slot into the right spot instead of reusing an old level.
Then add titles anywhere:
<script>
import { Title } from 'svelte-title'
</script>
<Title title="Dashboard" />
<!-- Result: "Dashboard • App" -->Nested layouts work too:
<!-- settings/+layout.svelte -->
<Title title="Settings" />
<!-- settings/billing/+page.svelte -->
<Title title="Billing" />
<!-- Result: "Billing • Settings • App" -->Want something other than the default bullet (•)? Pass a separator prop to your root layout:
<!-- src/routes/+layout.svelte -->
<Title title="App" separator=" | " />Now all your titles will use pipes:
<Title title="Settings" />
<!-- Result: "Settings | App" -->Only the layout at level 0 (your root layout) should set separator. Nested titles inherit whatever the root provides.
The <Title> component takes these props:
title(required) - The title textseparator(optional) - Custom separator (root layout only)override(optional) - Show only this title, no cascadinglevel(optional) - Force a specific hierarchy level
<!-- Override mode - just shows "Standalone" -->
<Title title="Standalone" override={true} />The component automatically detects hierarchy based on render order. Root layouts get level 0, nested layouts get level 1+, and pages get the highest levels. Titles build from specific to general.
Don't put multiple <Title> components in the same file—use one per layout or page so each view renders the expected title order.
DEFAULT_SEPARATOR- The default bullet separator, handy if you want to reuse it elsewhereclearTitleState()- Clears every title and the separator; useful for SSR hooks or test setup
- When you build without SSR: Add a fallback
<title>inapp.htmlso visitors never see just the domain while the app boots. - When you use SSR: Any hard-coded
<title>tag insideapp.htmlwins over the component’s SSR output, so keep that file blank or neutral. - Route changes: Keep the
resetLevelCounter()effect in place so automatic levels keep producing the right cascade as users navigate.
- Keep
resetLevelCounter()in your root layout effect (shown above) so every route change recalculates the title levels correctly. - If you handle SSR manually (most apps can skip this), clear the state for every request. For example:
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit'
import { clearTitleState } from 'svelte-title'
export const handle: Handle = async ({ event, resolve }) => {
clearTitleState()
return resolve(event)
}- SvelteKit 2.0+
- Svelte 5.0+ (uses runes)
Found a bug or have a feature request? Please open an issue on GitHub.
MIT