Skip to content
Kappa
Content Loader
@dicehub/kappav0.4.2

Content Loader

Show loading, empty, error, or ready content in the same region.

Simulation cases
Loading cases…
Loading cases…
<script setup lang="ts">
import { ContentLoader } from "@dicehub/kappa/components/content-loader";
import { ref } from "vue";
const state = ref<"ready" | "loading" | "empty" | "error">("loading");

async function loadCases() {
  state.value = "loading";
  // Load data in your application, then set ready, empty, or error.
}
</script>

<template>
  <ContentLoader :state="state" loading-label="Loading cases…"
    empty-title="No cases yet" retryable @retry="loadCases">
    <CaseList :items="cases" />
  </ContentLoader>
</template>

Installation

import { ContentLoader } from "@dicehub/kappa";
// Or: "@dicehub/kappa/components/content-loader"

Composition

Content Loader composes Kappa Loader, Empty, and Button. The application sets one state and owns data requests, cancellation, and retries. The component does not fetch data or infer whether a response is empty.

The default slot renders only in the ready state. With keep-mounted, content mounts on the first ready state and remains hidden during later loading, empty, or error states. Controls inside that hidden region cannot receive keyboard focus. Close any open dialogs or dropdowns before changing state: their portalled content lives outside the hidden region. Avoid this option when keeping a large view mounted is expensive.

Examples

Compact

Use a shorter placeholder inside side panels and small cards.

Simulation cases
Loading cases…
Loading cases…
<ContentLoader state="loading" compact loading-label="Loading cases…" />

Custom states

Replace each state with a skeleton, guidance, or recovery controls.

Simulation cases
Loading cases…
<ContentLoader :state="state" @retry="loadCases">
  <template #loading><SkeletonLine /></template>
  <template #empty>
    <p>No cases match the current filter.</p>
    <Button @click="clearFilter">Clear filter</Button>
  </template>
  <template #error="{ retry }">
    <p>The case service is unavailable.</p>
    <Button @click="retry">Reconnect</Button>
  </template>
  <CaseList :items="cases" />
</ContentLoader>

Preserve mounted content

Write a note, then refresh. The input DOM node and its local state remain intact.

Run note
The input stays mounted during refresh.
<ContentLoader :state="state" keep-mounted>
  <Input v-model="note" aria-label="Run note" />
</ContentLoader>

Accessibility

The content region exposes aria-busy while loading. A separate, persistent status region announces the current state without moving focus. Default labels can be translated with props. Custom slots reuse these announcements; avoid a second live region with the same message.

When a refresh removes a focused control, the application should move focus to a stable control. Loader respects reduced-motion preferences. Empty and error states use no animation.

API Reference

state: ready | loading | empty | error, default ready. compact, keepMounted, and retryable default to false. Native attributes and listeners reach the root.

Text props: loadingLabel (“Loading…”), emptyTitle (“No results”), emptyDescription (“There is no content to show.”), errorTitle (“Unable to load content”), errorDescription (“Try again in a moment.”), and retryLabel (“Try again”).

Slots: default (ready content), loading, empty, and error. The error slot receives retry(), which emits the retry event. The default retry button appears only with retryable. Set the next state in the application handler.

Set --kappa-content-loader-min-height to reserve placeholder space. The default is 10rem, or 6rem with compact spacing. Ready content uses its natural height.

Exports: ContentLoader, ContentLoaderProps, ContentLoaderState, ContentLoaderSlots, ContentLoaderEmits, and CONTENT_LOADER_STATES.