Skip to main content

Guides

bits-ui & melt recipes

Compose svelte-pdf-mini parts with bits-ui and melt-ui primitives, and build a shadcn-style kit.


svelte-pdf-mini follows bits-ui’s conventions — compound parts, child snippets, ref, data-* attributes, controlled and uncontrolled props — so both compose without adapters.

A bits trigger rendering one of our parts

bits passes its trigger props (ARIA attributes, handlers, the ref attachment); our part merges them with its own and chains the handlers.

<Tooltip.Trigger>
	{#snippet child({ props })}
		<Zoom.In {...props}><span class="icon-[lucide--zoom-in]"></span></Zoom.In>
	{/snippet}
</Tooltip.Trigger>
<Tooltip.Trigger>
	{#snippet child({ props })}
		<Zoom.In {...props}><span class="icon-[lucide--zoom-in]"></span></Zoom.In>
	{/snippet}
</Tooltip.Trigger>

Our part rendering your component

<PageNav.Next>
	{#snippet child({ props })}
		<Button {...props} size="icon" aria-label="Next page"><span class="icon-[lucide--chevron-right]"></span></Button>
	{/snippet}
</PageNav.Next>
<PageNav.Next>
	{#snippet child({ props })}
		<Button {...props} size="icon" aria-label="Next page"><span class="icon-[lucide--chevron-right]"></span></Button>
	{/snippet}
</PageNav.Next>

Controlled state

Viewer state is bindable, so bits primitives can drive it:

<Select.Root type="single" bind:value={scrollMode}>…</Select.Root>
<Slider.Root type="single" bind:value={() => viewer.zoom, (z) => viewer.zoomTo(z)}>…</Slider.Root>
<Select.Root type="single" bind:value={scrollMode}>…</Select.Root>
<Slider.Root type="single" bind:value={() => viewer.zoom, (z) => viewer.zoomTo(z)}>…</Slider.Root>

Use viewer.zoomTo() rather than assigning zoom when you want the eased transition.

Transitions

Both libraries use the same pattern: forceMount, the child snippet’s open flag, and a Svelte transition on your element. See Floating parts.

melt-ui table of contents

melt’s createTableOfContents (from @melt-ui/svelte) observes real headings. Add <Paper.Headings /> to each page — hidden h2–h6 at every section, with ids section-<id> — and point the builder at the pages container:

const {
	elements: { item },
	states: { headingsTree, activeHeadingIdxs }
} = createTableOfContents({
	selector: '#pages',
	scrollFn: (id) => paper.goToSection(paper.flatSections.find((s) => `section-${s.id}` === id)!),
	pushStateFn: () => {}
});
// <a {...$item(id)} use:item> — no preprocessor needed
const {
	elements: { item },
	states: { headingsTree, activeHeadingIdxs }
} = createTableOfContents({
	selector: '#pages',
	scrollFn: (id) => paper.goToSection(paper.flatSections.find((s) => `section-${s.id}` === id)!),
	pushStateFn: () => {}
});
// <a {...$item(id)} use:item> — no preprocessor needed

A recipe kit

The docs keep styled recipes in apps/docs/src/lib/demos/components/ui/: Button, Tip (tooltip with shortcut), ToggleGroup, Menu, ZoomSelect, ZoomSlider, Tabs, Dialog, Kbd and an accessible ColorPicker. Copy them into your app and restyle.