GrEmptyState

  • core
  • feedback
  • API · 6 props

Explains an empty state and suggests the user’s next step.

import { GrEmptyState } from '@feugene/granularity/components/GrEmptyState'

Overview

When to take it

  • the list is empty from the very beginning — “no payouts yet”: an explanation and a next step instead of an empty rectangle;
  • the filter found nothing — the text and the action are different here: reset the filter, not create the first record;
  • the section has not been set up yet — an icon, a heading, a description and a button instead of an instruction from support;
  • the state is built into another component — lists, tables and charts show it through the #empty slot.

When to take something else

NeedTake
The data is loadingGrSkeleton / GrLoading
The request failedGrResponseErrorBanner
A warning in the flow of the pageGrAlert
Just text with no illustration is neededGrCard

An empty screen without an explanation reads as a breakage. The difference between “there is nothing here yet” and “nothing was found for this filter” matters: in the first case you are offered to create something, in the second to remove the filter.

Variants every tone and view, rendered by the real component

variant

  • ghost
  • outlined

Sizes

  • xs
  • sm
  • md
  • lg

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

  • The first day of an invoice list

    Where the rows will be, the empty state offers the actions that fill them: the default slot holds the buttons, and the description says what happens next rather than that the list is empty. variant="ghost" because the list is already a frame; once an invoice exists, the list replaces the emptiness.

    Invoices

    No invoices yet

    Create the first one, or bring last year's from a CSV. Clients get each invoice by email with a payment link.
  • An empty card that is good news

    Inside a card, size="sm" with variant="ghost" keeps the emptiness from drawing a second frame and a big picture. The icon slot puts a green shield instead of a blank page — no disputes is the answer the reader hoped for — and heading-level follows the card’s own heading.

    Recent payments

    Northwind2 min ago€1,284.50
    Contoso1 h ago€432.00
    Fabrikamyesterday€96.00

    Open disputes

    No open disputes

    Chargebacks appear here within an hour of the bank's notice.

API generated from the package · cannot drift

Props

PropTypedefaultDescription
variant"ghost" | "outlined" | undefinedundefined`ghost` removes the border and the background: a card inside a card draws a second border.
titlestring | undefinedundefinedThe heading. Unset — it comes from the locale; the `#title` slot is stronger than both.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe scale of the icon, the text and the spacing. `md` by default; taken from `GrConfigProvider`.
descriptionstring | undefinedundefinedAn explanation under the heading: why it is empty and what to do. The `#description` slot is stronger.
headingLevel2 | 3 | 4 | 5 | 6 | undefinedundefinedThe heading level (`h2`…`h6`) to fit the structure of the page. `3` by default.
kind"search" | "empty" | undefined"empty"What kind of emptiness it is: nothing has been created yet (`empty`) or nothing was found for the query (`search`). Changes the default icon and title; a given `title` is stronger in both cases.

Slots

SlotTypeDescription
iconanyAn icon instead of the built-in one.
titleanyThe heading as markup: a link, an emphasis, a counter.
descriptionanyThe explanation as markup — with a link or an emphasis; stronger than `description`.
defaultanyThe actions under the text: they are centred.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrEmptyStateProps, GrEmptyStateConfigurableProps, GrEmptyStateSize } from '@feugene/granularity/components/GrEmptyState'

Filedist/types/components/GrEmptyState/GrEmptyState.vue.d.ts

GrEmptyStatePropsinterface
interface GrEmptyStateProps {
    title?: string;
    description?: string;
    size?: GrEmptyStateSize;
    variant?: GrEmptyStateVariant;
    headingLevel?: GrEmptyStateHeadingLevel;
    kind?: GrEmptyStateKind;
}

Filedist/types/components/GrEmptyState/config.d.ts

grEmptyStateConfigconst
const grEmptyStateConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrEmptyState">;

Filedist/types/components/GrEmptyState/defaults.d.ts

GrEmptyStateConfigurablePropsinterface
interface GrEmptyStateConfigurableProps {
    size: GrEmptyStateSize;
    variant: GrEmptyStateVariant;
    headingLevel: GrEmptyStateHeadingLevel;
}

Filedist/types/components/GrEmptyState/grEmptyStateStyles.d.ts

GrEmptyStateSizetype
type GrEmptyStateSize = GrComponentSize;
GrEmptyStateVarianttype
type GrEmptyStateVariant = typeof GR_EMPTY_STATE_VARIANTS[number];
GrEmptyStateKindtype
type GrEmptyStateKind = typeof GR_EMPTY_STATE_KINDS[number];
GrEmptyStateHeadingLeveltype
type GrEmptyStateHeadingLevel = typeof GR_EMPTY_STATE_HEADING_LEVELS[number];

Filedist/types/components/GrEmptyState/safelist.d.ts

grEmptyStateSafelistconst
const grEmptyStateSafelist: string[];

Usage from the library prose

Two emptinesses, not one

kind tells apart what the user sees differently:

kindWhenDefault heading and icon
empty (the default)nothing has been created yet“Nothing here yet”, a box
searchnothing was found for the query or the filter“Nothing found”, a magnifier with a cross

The difference is not cosmetic. Under a filter “Nothing here yet” misinforms: the data is there, it merely did not match, and the user’s next step is different — not “create” but “loosen the query”.

There are exactly two values because that is how many recur. A loading error is deliberately not among them: the package has components of its own for it — GrResponseErrorBanner.

A default description is not substituted in either kind: it is always about a specific screen, and there is nothing to guess it from.

The heading
vue
<GrEmptyState title="No payouts yet" description="Create the first payout to track approvals.">
  <GrButton size="sm">Create a payout</GrButton>
</GrEmptyState>

The heading is a real heading: without it an empty state is not found by navigating across headings, and it is exactly the empty state that explains what is happening on the screen. The level is set by headingLevel (2…6, 3 by default) and configured globally through componentDefaults.GrEmptyState.headingLevel. There is no h1 in the set on purpose: an empty state lives inside an already existing structure of the page rather than setting it.

title is optional: without it and without the slot a string from the locale is taken (gr.emptyState.title), so that <GrEmptyState /> does not turn into an empty card.

The slots
SlotWhat for
titlethe heading as markup: a link, an emphasis, a counter
descriptionthe description as markup
iconan icon of your own instead of the built-in lucide/inbox
the default onethe actions under the text, centred

A slot is stronger than the prop of the same name. The built-in icon is decorative (aria-hidden) — the meaning is carried by the text.

The surface
vue
<GrCard>
  <GrEmptyState variant="ghost" title="Nobody has been invited" />
</GrCard>

variant (outlined by default, ghost) repeats the vocabulary of GrCard: ghost removes the border, the background and the radius. It is needed inside an already existing card — in GrDataTable #empty, in GrList #empty or in a layout of your own, where outlined gives a second border around the same surface.

The size

size (xs…lg, md by default, read from GrConfigProvider) moves the padding of the card, the box of the icon, the icon itself, the type size of the heading and the vertical spacing. xs and sm are for an empty table cell and a dropdown panel, where a full-size state takes up the whole list.

Styling

Everything comes from tokens: the radius from --gr-radius-md/--gr-radius-lg by step, the type sizes from --gr-text-xs/--gr-text-sm/--gr-text-base, the colours from --gr-card, --gr-brd, --gr-muted, --gr-muted-fg.