# EmptyState

**Maturity:** experimental

Show something purposeful instead of a blank screen when a list, search, or feed has nothing to render.

## Import

```tsx
import { EmptyState } from "@liquidify/react/empty-state"
```

## Props

- `children`: `ReactNode`. Freeform slot the caller composes their own content into (SwiftUI `actions: () -> Actions`) — typically `<Button>`s, e.g. `variant="borderedProminent"` for the primary action. Rendered as a direct flex child so it respects the root's column alignment; EmptyState enforces no structure on it.
- `className`: `string | undefined`. Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.
- `description`: `ReactNode`. Secondary explanatory copy (SwiftUI `description: () -> Text?`). A string/number renders through the component's own token-styled text element; a `ReactNode` passes through unwrapped.
- `icon`: `ReactNode`. Decorative glyph, e.g. `<Icon name="bookmark" />` (SwiftUI `systemImage:` / `image:`). Always `aria-hidden` regardless of what the supplied node itself sets.
- `ref`: `Ref<HTMLElement> | undefined`. Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.
- `title`: `string` (required). The accessible name — required; rendered as the heading-level title.

## SwiftUI mapping

- ``init(_ title:, systemImage:, description:)`` → ``title: string` + `icon?: ReactNode` + `description?: ReactNode`` (direct): `systemImage:` → `icon` accepts a `ReactNode` (e.g. `<Icon name="bookmark" />`), never a raw SF-Symbol string — no Apple glyph vectors ship in this package (`icon.tsx`, SF Symbols License).
- ``init(_ title:, image:, description:)`` → `same as above` (renamed): The `Image` overload collapses into the same `icon` `ReactNode` slot.
- ``init(label:, description:, actions:)`` → ``title` + `icon` (in place of the raw `label:` ViewBuilder) + `description` + `children`` (direct): Full builder. SwiftUI's icon+title `Label` is modelled as the idiomatic `title`+`icon` pair; **no raw `label` escape hatch** (see Out of scope).
- ``description: () -> Text?`` → ``description?: ReactNode`` (direct): Secondary copy. A string/number renders through the component's own token-styled text element (`--lq-font-size-body`, secondary label colour); a `ReactNode` passes through unwrapped.
- ``actions: () -> Actions` (ViewBuilder of `Button`s)` → ``children?: ReactNode`` (direct): A generic children slot — mirrors the SwiftUI ViewBuilder's flexibility. The caller composes its own `<Button>`s (e.g. `variant="borderedProminent"` for the primary, `variant="plain"` for the secondary); EmptyState does **not** enforce a structured `primaryAction`/`secondaryAction` pair.
- ``static func search(text:)`` → ``title` + `icon={<Icon name="search" />}` + `description`` (direct): No dedicated preset — the caller supplies the localized "No Results for …" title and copy directly.
