Nasaq
Components

EmptyState

Empty, error and loading states for a region (dashed frame with icon, title, description and actions; skeleton rows), plus the Skeleton primitive.

PreviewOpen ↗

Code

import { Button, EmptyState } from "@fadymondy/nasaq/web";export function NoIssues() {  return (    <EmptyState      title="لا توجد مهام بعد"      description="تظهر هنا المهام التي تنشئها أو تُسند إليك."      actions={<Button variant="primary">مهمة جديدة</Button>}    />  );}

Loading & States · stable

Live examples and controls: EmptyState in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/states.json

Four exports for the three states every data region has:

  • EmptyState: nothing to show yet.
  • ErrorState: loading failed. Carries an icon and words, never colour alone.
  • LoadingState: skeleton rows that preview the layout that is coming.
  • Skeleton: the single placeholder block used by LoadingState, for custom loading layouts.

EmptyState and ErrorState share one frame: a dashed, card-radius box with an optional icon tile, a title, a description and an action row.

When to use

  • A list, table or panel has no data: EmptyState, with the action that fixes it.
  • A request failed and the user can retry: ErrorState.
  • A region is loading and you know roughly its shape: LoadingState or Skeleton.

When not to use

  • Inline pending work (a button saving, a refresh): use Spinner.
  • A transient message or confirmation: use a toast.
  • A status of one item: use Status or Badge.
  • Decorative empty art on populated screens: leave an empty cell instead.

Import

import {
  EmptyState, ErrorState, LoadingState, Skeleton, type LoadingStateProps,
} from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { Button, EmptyState } from "@fadymondy/nasaq/web";

export function NoIssues() {
  return (
    <EmptyState
      title="لا توجد مهام بعد"
      description="تظهر هنا المهام التي تنشئها أو تُسند إليك."
      actions={<Button variant="primary">مهمة جديدة</Button>}
    />
  );
}

Anatomy

EmptyState        data-slot="empty-state"      dashed frame, role none
ErrorState        data-slot="error-state"      role="alert"
├─ icon tile      40px, rounded-control, lucide icon (aria-hidden); red text in ErrorState
├─ title          <p class="text-label">
├─ description    <p class="text-body-sm text-muted-foreground">   (max-w-sm column)
├─ children       anything extra
└─ actions        wrapping, centred row

LoadingState      data-slot="loading-state"    role="status", aria-live="polite"
├─ sr-only label
├─ skeleton × rows       data-shape="rows" (rows), "grid" (loading-card), "timeline" (loading-event)
│                         (or spinner + label when rows is 0)
└─ caption               visible text under the skeleton, when set

Skeleton          data-slot="skeleton"         aria-hidden

API

EmptyState and ErrorState

Both accept the same props (the exported StateProps interface): Omit<ComponentProps<"div">, "title"> plus:

PropTypeDefaultDescription
titleReactNoderequiredHeadline.
icon?LucideIconInbox (EmptyState), CircleAlert (ErrorState)Glyph in the icon tile.
description?ReactNodenoneSupporting text under the title.
actions?ReactNodenoneUsually one primary Button and at most one secondary.
hatch?booleanfalseHatched ground (grid expression); off automatically in the native expression.
children?ReactNodenoneExtra content between the text and the actions.
className?stringnoneMerged onto the frame.

ErrorState also sets role="alert".

LoadingState

LoadingStateProps extends ComponentProps<"div">.

PropTypeDefaultDescription
label?string"Loading…" / "جارٍ التحميل…" by provider localeAnnounced to assistive tech; also shown next to the spinner when rows is 0.
rows?number3Number of skeleton items (rows, cards or events). With 0, a centred spinner and label are shown instead.
shape?"rows" | "grid" | "timeline""rows"The layout being previewed: list rows, a grid of cards, or a timeline of events.
columns?1 | 2 | 3 | 43Grid columns from the sm breakpoint up; one column below it.
caption?ReactNodenoneVisible text under the skeleton ("Fetching the last 30 days…"). Replaces the sr-only label as the announced text.
className?stringnoneMerged onto the wrapper.

Skeleton

({ className, ...props }: ComponentProps<"div">). A bg-secondary block with rounded-[4px] that pulses only when motion is allowed (motion-safe:animate-pulse). Give it a size with className.

Examples

Grid and timeline

<LoadingState shape="grid" columns={3} rows={6} />
<LoadingState shape="timeline" rows={4} caption="Fetching the audit trail…" />

Error with retry

import { Button, ErrorState } from "@fadymondy/nasaq/web";

export function LoadFailed({ retry }: { retry: () => void }) {
  return (
    <ErrorState
      title="تعذّر تحميل المهام"
      description="لم يستجب الخادم."
      actions={<Button onClick={retry}>حاول مرة أخرى</Button>}
    />
  );
}

Loading a list

import { LoadingState } from "@fadymondy/nasaq/web";

export function IssuesLoading() {
  return <LoadingState rows={4} label="جارٍ التحميل…" className="max-w-md" />;
}

Custom skeleton

import { Skeleton } from "@fadymondy/nasaq/web";

export function CardSkeleton() {
  return (
    <div className="flex flex-col gap-2">
      <Skeleton className="h-4 w-40" />
      <Skeleton className="h-3 w-64" />
    </div>
  );
}

Choosing the state in a data region

import { Button, EmptyState, ErrorState, LoadingState } from "@fadymondy/nasaq/web";

type Query = { isLoading: boolean; isError: boolean; data?: string[]; refetch: () => void };

export function Issues({ q }: { q: Query }) {
  if (q.isLoading) return <LoadingState rows={4} label="جارٍ التحميل…" />;
  if (q.isError) return <ErrorState title="تعذّر التحميل" actions={<Button onClick={q.refetch}>إعادة المحاولة</Button>} />;
  if (!q.data?.length) return <EmptyState title="لا توجد مهام" actions={<Button variant="primary">مهمة جديدة</Button>} />;
  return <ul>{q.data.map((i) => <li key={i}>{i}</li>)}</ul>;
}

Accessibility

None of these are focusable; keyboard access is through the actions you pass.

KeyAction
Tab / Enter / SpaceReach and activate the buttons in actions.
  • ErrorState has role="alert", so it is announced when it is mounted. It shows an icon and words, not colour alone.
  • LoadingState has role="status" and aria-live="polite" with an sr-only label. The skeleton rows are aria-hidden.
  • Icons are aria-hidden. The title and description carry the meaning.
  • Localise title, description, action labels and LoadingState's label (its default follows the provider locale).
  • Skeleton is aria-hidden; pair custom skeletons with a role="status" label.

RTL & i18n

  • Content is centred, so it needs no mirroring. The action row wraps and follows dir.
  • Arrows inside action buttons follow the button's own rules.
  • Numbers inside copy: use Num from the numeric component.
  • Built-in string: LoadingState label defaults to "Loading…", or "جارٍ التحميل…" when the provider locale starts with ar.
  • The ErrorState icon tile (data-slot="state-icon") is coloured directly with text-nq-danger-text.

Styling & tokens

  • Frame: border-dashed border-border, rounded-card; icon tile bg-card, rounded-control; ErrorState icon text-nq-danger-text.
  • Skeleton: bg-secondary. Row height h-row.
  • Target with [data-slot=empty-state], [data-slot=error-state], [data-slot=loading-state], [data-slot=skeleton].
  • Extend with className. Do not colour with raw hex.

Do / Don't

  • Do say what is missing and give the action that fixes it.
  • Do prefer skeletons over spinners for regions; they preview the layout.
  • Do give errors an icon and words.
  • Don't add empty-state art or "welcome" filler on populated screens.
  • Don't show more than one primary action.
  • Don't rely on the default "Loading…" label in Arabic.

Lab

https://docs.nasaqui.com/?path=/docs/components-loading-states-states--docs

On this page