Svelte
The Svelte adapter provides a <Hollow> component that integrates with Svelte's reactivity model, using stores, slots, and event dispatching for empty state rendering.
Installation
Import from the hollows-ui/svelte subpath. The adapter supports Svelte 4 and Svelte 5 (runes mode).
npm install --save-dev hollows-uiImport the generated registry in your app entry point:
import './hollows/registry'import App from './App.svelte'const app = new App({ target: document.getElementById('app')!,})export default appBasic usage
Wrap any data-dependent section with the <Hollow> component. When empty is true, it renders the auto-generated empty state with illustration, copy, and CTA.
1<script lang="ts">2 import { Hollow } from 'hollows-ui/svelte'3 import TaskCard from './TaskCard.svelte'45 export let tasks: Task[] = []6 export let isLoading = false78 $: isEmpty = !isLoading && tasks.length === 09</script>1011<div class="task-list">12 <h2>My Tasks</h2>13 <Hollow14 name="task-list"15 empty={isEmpty}16 loading={isLoading}17 on:action={() => goto('/tasks/new')}18 >19 {#each tasks as task (task.id)}20 <TaskCard {task} />21 {/each}22 </Hollow>23</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | - | Unique identifier matching the generated definition |
| empty | boolean | false | Whether to show the empty state |
| loading | boolean | false | Show skeleton loading state |
| category | string | auto | Override the auto-detected category |
| copy | CopyOverride | - | Override generated copy |
| animate | boolean | true | Enable transition animations |
| class | string | - | Additional CSS class for the container |
Event dispatching
The component dispatches an action event when the CTA button is clicked. Listen with Svelte's on: directive.
<script lang="ts"> import { Hollow } from 'hollows-ui/svelte' import { goto } from '$app/navigation' export let projects: Project[] = []</script><Hollow name="project-list" empty={projects.length === 0} on:action={() => goto('/projects/new')}> {#each projects as project (project.id)} <ProjectCard {project} /> {/each}</Hollow>You can also forward the event to a parent component:
<script lang="ts"> import { createEventDispatcher } from 'svelte' import { Hollow } from 'hollows-ui/svelte' const dispatch = createEventDispatcher() export let items: Item[] = []</script><Hollow name="item-list" empty={items.length === 0} on:action={() => dispatch('create')}> {#each items as item (item.id)} <ItemRow {item} /> {/each}</Hollow>Slot customization
Use named slots to customize the empty state rendering. The empty slot receives the generated state as slot props.
1<Hollow name="notifications" empty={notifications.length === 0}>2 <!-- Default slot: normal content -->3 {#each notifications as notification (notification.id)}4 <NotificationItem {notification} />5 {/each}67 <!-- Named slot: custom empty state -->8 <svelte:fragment slot="empty" let:state>9 <div class="flex flex-col items-center py-12 text-center">10 <BellOff class="w-16 h-16 text-gray-400 mb-4" />11 <h3 class="text-lg font-semibold">{state.copy.headline}</h3>12 <p class="text-gray-500 mt-2 max-w-xs">13 {state.copy.description}14 </p>15 <button16 class="mt-6 px-4 py-2 bg-amber-500 text-black rounded-lg"17 on:click={() => enableNotifications()}18 >19 {state.copy.cta.label}20 </button>21 </div>22 </svelte:fragment>2324 <!-- Named slot: custom illustration -->25 <svelte:fragment slot="illustration">26 <LottiePlayer src="/animations/empty-bell.json" width={200} />27 </svelte:fragment>28</Hollow>Store integration
Svelte stores work naturally with the empty prop. Use derived stores for complex empty state logic.
1<script lang="ts">2 import { derived } from 'svelte/store'3 import { Hollow } from 'hollows-ui/svelte'4 import { items, searchQuery } from '$lib/stores'56 const filteredItems = derived(7 [items, searchQuery],8 ([$items, $query]) =>9 $items.filter((item) =>10 item.name.toLowerCase().includes($query.toLowerCase())11 )12 )1314 const isEmpty = derived(15 [filteredItems, searchQuery],16 ([$filtered, $query]) => $query.length > 0 && $filtered.length === 017 )18</script>1920<Hollow21 name="search-results"22 empty={$isEmpty}23 copy={{ headline: `No results for "${$searchQuery}"` }}24 on:action={() => searchQuery.set('')}25>26 {#each $filteredItems as item (item.id)}27 <ItemCard {item} />28 {/each}29</Hollow>SvelteKit
With SvelteKit, import the registry in your root layout and use the hollow component in any page or component.
<script> // Import once at the root layout import '../hollows/registry'</script><slot />1<script lang="ts">2 import { Hollow } from 'hollows-ui/svelte'3 import type { PageData } from './$types'45 export let data: PageData67 $: messages = data.messages8</script>910<div class="max-w-2xl mx-auto p-6">11 <h1 class="text-2xl font-bold mb-6">Inbox</h1>12 <Hollow13 name="user-inbox"14 empty={messages.length === 0}15 on:action={() => goto('/compose')}16 >17 {#each messages as message (message.id)}18 <MessageCard {message} />19 {/each}20 </Hollow>21</div>For server-side rendered pages, the hollow component renders the empty state on the server when the empty prop is true during SSR. The illustration is inlined as an SVG, so no additional network requests are needed.
TypeScript
The Svelte adapter ships with full TypeScript definitions. Use lang="ts" in your script blocks for type checking.
import type { HollowProps, HollowState, CopyOverride } from 'hollows-ui/svelte'// Types are available for use in your components// All props and events are fully typed