App setup
Global setup
You can define a setup function globally in your setup file defined by the setupFile option in the global configuration (learn more).
For Svelte it is called setupSvelte. Poveste provides an optional defineSetupSvelte helper to have better types in your IDE:
// poveste.setup.ts
import { defineSetupSvelte } from '@poveste/plugin-svelte'
import './poveste.css'
export const setupSvelte = defineSetupSvelte(({ app, story, variant }) => {
// Runs for every mounted story and variant
document.documentElement.dataset.theme = 'dark'
})TIP
Importing global CSS or JS files at the top of the setup file — outside the hook — is the most common use, and it does not need the hook at all. The file is a module like any other.
If you already export a numbered name
setupSvelte3, setupSvelte4 and setupSvelte5 are all still accepted, and nothing needs changing today. They are aliases of one hook, not separate hooks, so Poveste runs the first one it finds and warns about the rest:
// Both of these are the same hook. Only `setupSvelte5` runs, and the warning says so.
export function setupSvelte5() { /* … */ }
export function setupSvelte() { /* … */ }Established names win, so adding setupSvelte to a file that already exports setupSvelte5 changes nothing until you delete the old one. Rename when it suits you; keep one.
The numbers were always historical. @poveste/plugin-svelte supports Svelte 5 only, so setupSvelte3 and setupSvelte4 name majors it cannot run — they are accepted because histoire users wrote them, not because they select anything. The defineSetupSvelte helper is unnumbered, and defineSetupSvelte3 / defineSetupSvelte4 / defineSetupSvelte5 are aliases of it.
What the hook receives
app | what Svelte's mount() returned — the mounted component instance |
story | the story being rendered |
variant | the variant, or null for the story-level mount that fills the controls panel |
Two things follow from app being a component instance rather than a Vue-style application object, and they are the main differences from the Vue page:
- There is no
app.use(),app.component()orapp.provide(). Svelte has no application-level plugin API to call. - The hook runs after the component is mounted, not before it. It cannot supply anything your component needs while initialising — by the time it runs, the component already has. Use it for side effects on the surrounding document, and put anything a component must receive at construction into the story itself.
Local setup
A variant can define a setupApp prop, called after the global hook with the same argument:
<script>
const { Hst } = $props()
function setupApp({ variant }) {
document.body.dataset.variant = variant.title
}
</script>
<Hst.Story title="Story setup">
<Hst.Variant title="Local setup" {setupApp}>
<MyComponent />
</Hst.Variant>
</Hst.Story>Examples
Shared state
A Svelte store is an ordinary module, so a story imports it and nothing has to be registered:
// lib-store.ts
import { writable } from 'svelte/store'
export const myValue = writable(10)<!-- Store.svelte -->
<script lang="ts">
import { myValue } from '../lib-store.js'
</script>
<button onclick={() => myValue.update(value => value + 1)}>+1</button>
<span>{$myValue}</span><!-- Store.story.svelte -->
<script lang="ts">
import type { Hst as HstType } from '@poveste/plugin-svelte'
import Store from './Store.svelte'
const { Hst }: { Hst: HstType } = $props()
</script>
<Hst.Story title="Store">
<Store />
</Hst.Story>This is where Svelte is shorter than Vue rather than less capable: the Vue page registers Pinia in the setup file with app.use(), because a Pinia store needs an application instance to attach to. A Svelte store does not, so there is no setup step to show.
The store is module state, so it is shared across every story in the book and survives navigation between them. Give each story a fresh store if that matters — export a factory rather than an instance, and call it in the story.
Providing context
setContext is the closest thing to Vue's app.provide(), and the two differ in a way that decides where you put it. app.provide() is called on the application, so the Vue setup hook can supply it. Svelte's context is set by a parent component during initialisation, and this plugin's hook runs after the component is mounted — so a global hook cannot provide it.
Set it from the story instead, in a wrapper component:
<!-- ThemeProvider.svelte -->
<script lang="ts">
import { setContext } from 'svelte'
const { theme = 'dark' } = $props()
setContext('theme', theme)
</script>
<slot /><!-- MyComponent.story.svelte -->
<script lang="ts">
import type { Hst as HstType } from '@poveste/plugin-svelte'
import MyComponent from './MyComponent.svelte'
import ThemeProvider from './ThemeProvider.svelte'
const { Hst }: { Hst: HstType } = $props()
</script>
<Hst.Story title="With context">
<ThemeProvider theme="dark">
<MyComponent />
</ThemeProvider>
</Hst.Story>A component calling getContext('theme') with no provider above it gets undefined, which usually surfaces further down as a property read on undefined rather than as a missing-context error — so if a component works in your app and not in its story, this is the first thing to check.
The same shape covers anything a component needs at construction. It is also what to reach for until addWrapper exists, since there is no way to apply it to every story at once.
SvelteKit
The setup file is configured the same way, under the poveste key of your Vite config — see SvelteKit:
export default defineConfig({
plugins: [sveltekit()],
poveste: {
plugins: [HstSvelte()],
setupFile: '/src/poveste.setup.ts',
},
})Poveste mounts your story component directly, so SvelteKit's routing is not involved: no +layout.svelte wraps your story, and no +page.ts load runs. Anything a layout would have provided has to come from the story — wrap the component under test in the story body, the same as you would for any other provider.
i18n
There is nothing special to do. A Svelte i18n library — or a hand-rolled t() — is an ordinary module, not a framework plugin, so it has none of the sandbox trouble the Nuxt i18n guide describes: nothing gets booted through an app entry, so nothing 500s the iframe. Import or initialise it like any other module (in a .ts / .svelte.ts file, or in the setup file) and stories pick it up. examples/svelte carries a minimal version.