Skip to content

Async

A zero-boilerplate wrapper that drives the right UI for every stage of an async data fetch. It manages aria-busy, aria-live, and role automatically so screen readers always stay informed.

status defaults to success, so default slotted content is visible unless you explicitly set another state.

Status: Loading

The default loading view renders a skeleton stack automatically. No slot required.

PreviewCode
RTL
html
<ore-async status="loading" style="width: 100%; max-width: 24rem;"></ore-async>

Custom Loading Slot

PreviewCode
RTL
html
<ore-async status="loading" style="width: 100%; max-width: 24rem;">
  <div slot="loading" style="display: flex; gap: var(--size-3); align-items: center; padding: var(--size-4);">
    <ore-skeleton variant="circle" size="md"></ore-skeleton>
    <div style="flex: 1; display: flex; flex-direction: column; gap: var(--size-2);">
      <ore-skeleton variant="text" lines="1" width="60%"></ore-skeleton>
      <ore-skeleton variant="text" lines="1" width="40%"></ore-skeleton>
    </div>
  </div>
</ore-async>

Status: Empty

PreviewCode
RTL
html
<ore-async
  status="empty"
  empty-label="No results found"
  empty-description="Try adjusting your search or filters."
  style="width: 100%; max-width: 24rem;"></ore-async>

Custom Empty Slot

PreviewCode
RTL
html
<ore-async status="empty" style="width: 100%; max-width: 24rem;">
  <div
    slot="empty"
    style="display: flex; flex-direction: column; align-items: center; gap: var(--size-3); padding: var(--size-10) var(--size-6); text-align: center;">
    <ore-avatar size="xl" label="<ore-icon name="mail-open" size="16"></ore-icon>"></ore-avatar>
    <p style="font-size: var(--text-sm); color: var(--color-contrast-500);">Your inbox is empty</p>
    <ore-button variant="outline" size="sm">Compose message</ore-button>
  </div>
</ore-async>

Status: Error

PreviewCode
RTL
html
<ore-async
  status="error"
  error-label="Failed to load data"
  error-description="Check your connection and try again."
  retryable
  style="width: 100%; max-width: 24rem;"></ore-async>

Custom Error Slot

PreviewCode
RTL
html
<ore-async status="error" style="width: 100%; max-width: 24rem;">
  <div slot="error">
    <ore-alert color="error" variant="bordered" heading="Request failed">
      The server returned an error. Please try again later.
      <div slot="actions">
        <ore-button color="error" variant="outline" size="sm">Retry</ore-button>
      </div>
    </ore-alert>
  </div>
</ore-async>

Status: Success

PreviewCode
RTL
html
<ore-async status="success" style="width: 100%; max-width: 24rem;">
  <ore-card elevation="1" padding="md">
    <span slot="header">Latest activity</span>
    <p>Everything loaded successfully.</p>
  </ore-card>
</ore-async>

Retry

Add retryable to show a built-in retry button in the error state. Listen for the retry event to re-trigger your fetch.

PreviewCode
RTL
html
<ore-async
  id="data-region"
  status="error"
  error-label="Could not load items"
  retryable
  style="width: 100%; max-width: 24rem;"></ore-async>

<script type="module">
  import '@vielzeug/refine/async';

  document.getElementById('data-region').addEventListener('retry', () => {
    console.log('Retry triggered');
  });
</script>

Composing with ore-card

PreviewCode
RTL
html
<ore-card elevation="1" style="width: 100%; max-width: 24rem;">
  <span slot="header">Latest orders</span>
  <ore-async
    status="empty"
    empty-label="No orders yet"
    empty-description="Orders will appear here once placed."></ore-async>
</ore-card>

Composing with ore-table

Use the loading slot to render a table-shaped skeleton that matches the real table layout. When data arrives, switch status to success and the real table appears.

PreviewCode
RTL
html
<div style="display: flex; flex-direction: column; gap: var(--size-3); width: 100%;">
  <div style="display: flex; gap: var(--size-2);">
    <ore-button id="btn-load" size="sm" variant="outline">Simulate loading</ore-button>
    <ore-button id="btn-done" size="sm" variant="outline">Simulate success</ore-button>
  </div>

  <ore-async id="members-async" status="loading" style="width: 100%;">
    <!-- Loading slot: ore-table in loading state with ore-skeleton cells.
         ore-td element children are deep-cloned into the native shadow <td>,
         so ore-skeleton renders correctly inside the table structure. -->
    <ore-table slot="loading" striped bordered caption="Members" loading>
      <ore-tr head>
        <ore-th>Name</ore-th>
        <ore-th>Role</ore-th>
        <ore-th>Status</ore-th>
      </ore-tr>
      <ore-tr>
        <ore-td><ore-skeleton variant="text" lines="1" width="70%"></ore-skeleton></ore-td>
        <ore-td><ore-skeleton variant="text" lines="1" width="50%"></ore-skeleton></ore-td>
        <ore-td><ore-skeleton variant="text" lines="1" width="60%"></ore-skeleton></ore-td>
      </ore-tr>
      <ore-tr>
        <ore-td><ore-skeleton variant="text" lines="1" width="80%"></ore-skeleton></ore-td>
        <ore-td><ore-skeleton variant="text" lines="1" width="60%"></ore-skeleton></ore-td>
        <ore-td><ore-skeleton variant="text" lines="1" width="45%"></ore-skeleton></ore-td>
      </ore-tr>
      <ore-tr>
        <ore-td><ore-skeleton variant="text" lines="1" width="90%"></ore-skeleton></ore-td>
        <ore-td><ore-skeleton variant="text" lines="1" width="55%"></ore-skeleton></ore-td>
        <ore-td><ore-skeleton variant="text" lines="1" width="65%"></ore-skeleton></ore-td>
      </ore-tr>
    </ore-table>

    <!-- Default slot: real table shown on success -->
    <ore-table striped bordered caption="Members">
      <ore-tr head>
        <ore-th>Name</ore-th>
        <ore-th>Role</ore-th>
        <ore-th>Status</ore-th>
      </ore-tr>
      <ore-tr><ore-td>Alice</ore-td><ore-td>Admin</ore-td><ore-td>Active</ore-td></ore-tr>
      <ore-tr><ore-td>Bob</ore-td><ore-td>Editor</ore-td><ore-td>Active</ore-td></ore-tr>
      <ore-tr><ore-td>Carol</ore-td><ore-td>Viewer</ore-td><ore-td>Inactive</ore-td></ore-tr>
    </ore-table>
  </ore-async>
</div>

<script type="module">
  import '@vielzeug/refine/async';
  import '@vielzeug/refine/skeleton';
  import '@vielzeug/refine/table';
  import '@vielzeug/refine/button';

  const el = document.getElementById('members-async');
  document.getElementById('btn-load').addEventListener('click', () => {
    el.status = 'loading';
  });
  document.getElementById('btn-done').addEventListener('click', () => {
    el.status = 'success';
  });
</script>

Status values

statusWhat rendersaria-busyaria-live
idlenothing (empty region)falsepolite
loadingloading slot or skeleton stacktruepolite
emptyempty slot or built-in empty statefalsepolite
errorerror slot or built-in error statefalseassertive
successdefault slot (your content)falsepolite

Props

AttributeTypeDefaultDescription
statusAsyncStatus'success'Current data-fetch status
empty-labelstring'No content yet'Heading for the built-in empty state
empty-descriptionstringDescription below the empty-state heading
error-labelstring'Something went wrong'Heading for the built-in error state
error-descriptionstringDescription below the error-state heading
retryablebooleanfalseShow retry button in the built-in error state

Events

EventDetailDescription
retryFired when the built-in retry button is clicked

Slots

SlotDescription
(default)Rendered when status="success"
loadingReplaces the built-in skeleton stack during loading
emptyReplaces the built-in empty state illustration
errorReplaces the built-in error view

CSS Custom Properties

PropertyDefaultDescription
--async-color--color-contrast-500Icon/text color for built-in states
--async-icon-sizevar(--size-12)Icon size in built-in empty/error views
--async-gapvar(--size-3)Gap between elements in built-in views

Accessibility

ore-async manages ARIA on the host element automatically. While status is loading, aria-busy="true" is set so screen readers announce the busy region. In the error state, aria-live="assertive" ensures error messages interrupt the user immediately. All other states use aria-live="polite" so updates are announced after the current action completes.

The built-in error region uses role="alert" for immediate screen reader pickup, while the built-in loading and empty regions use role="status" for polite announcements. The retry button is typed type="button" to prevent accidental form submission.