Cascading page title manager for SvelteKit. It builds titles like "Page • Section • App" automatically as your layouts and pages render.
npm install svelte-title
First, 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<title>
in app.html
so visitors never see just the domain while the app boots.<title>
tag inside app.html
wins over the component’s SSR output, so keep that file blank or neutral.resetLevelCounter()
effect in place so automatic levels keep producing the right cascade as users navigate.resetLevelCounter()
in your root layout effect (shown above) so every route change recalculates the title levels correctly.// 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)
}
Found a bug or have a feature request? Please open an issue on GitHub.
MIT