Empty

Centered placeholder for empty content areas — no data, no results, no uploads.

Quick Start

<%= kui(:empty) do %>
  <%= kui(:empty, :header) do %>
    <%= kui(:empty, :media, variant: :icon) do %>
      <%= kiso_icon("folder") %>
    <% end %>
    <%= kui(:empty, :title) { "No Projects Yet" } %>
    <%= kui(:empty, :description) { "Get started by creating your first project." } %>
  <% end %>
<% end %>

Locals

The root empty has no variant axis — it renders a centered flex container.

Local Type Default
css_classes: String ""
**component_options Hash {}

Sub-parts

Part Usage Purpose
:header kui(:empty, :header) Groups media, title, and description
:media kui(:empty, :media) Icon or image container
:title kui(:empty, :title) Heading text
:description kui(:empty, :description) Muted description text
:actions kui(:empty, :actions) Centered button group for CTAs
:content kui(:empty, :content) General content area (inputs, links)

All sub-parts accept css_classes: and **component_options.

Anatomy

Empty
├── Header
│   ├── Media (icon or image)
│   ├── Title
│   └── Description
├── Actions (CTA buttons)
└── Content (general content)

All sub-parts are optional. Use any combination.

Usage

Media Variants

The :media sub-part has a variant: local to control how icons are displayed.

<%= kui(:empty, :media) do %>
  <svg>...</svg>
<% end %>

<%= kui(:empty, :media, variant: :icon) do %>
  <svg>...</svg>
<% end %>
Variant Appearance
default Transparent background, bare icon
icon Muted background, rounded container, auto-sized SVG

With Actions

Use the :actions sub-part for call-to-action buttons below the header. Buttons are automatically centered with gap-2 spacing and wrap on narrow screens.

<%= kui(:empty) do %>
  <%= kui(:empty, :header) do %>
    <%= kui(:empty, :media, variant: :icon) do %>
      <svg>...</svg>
    <% end %>
    <%= kui(:empty, :title) { "No Projects Yet" } %>
    <%= kui(:empty, :description) { "Get started by creating your first project." } %>
  <% end %>
  <%= kui(:empty, :actions) do %>
    <%= kui(:button) { "Create Project" } %>
    <%= kui(:button, variant: :outline) { "Import Project" } %>
  <% end %>
<% end %>

With Dashed Border

The base includes border-dashed (style only). Add border via css_classes: to show the dashed outline — useful for drop zones and upload areas.

<%= kui(:empty, css_classes: "border border-dashed") do %>
  <%= kui(:empty, :header) do %>
    <%= kui(:empty, :title) { "Upload Files" } %>
    <%= kui(:empty, :description) { "Drag and drop files here." } %>
  <% end %>
<% end %>

Inside a Card

Empty states work well nested inside Card content areas. Override flex-1 with flex-none when the empty state shouldn’t expand to fill the container.

<%= kui(:card) do %>
  <%= kui(:card, :header) do %>
    <%= kui(:card, :title) { "Recent Activity" } %>
  <% end %>
  <%= kui(:card, :content) do %>
    <%= kui(:empty, css_classes: "flex-none") do %>
      <%= kui(:empty, :header) do %>
        <%= kui(:empty, :title) { "No Activity" } %>
        <%= kui(:empty, :description) { "Activity will show up here." } %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Theme

Kiso::Themes::Empty = ClassVariants.build(
  base: "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance text-foreground md:p-12"
)

EmptyHeader      = ClassVariants.build(base: "flex max-w-sm flex-col items-center gap-2 text-center")
EmptyMedia       = ClassVariants.build(
  base: "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
  variants: { variant: { default: "bg-transparent", icon: "bg-muted text-foreground size-10 rounded-lg ..." } },
  defaults: { variant: :default }
)
EmptyTitle       = ClassVariants.build(base: "text-lg font-medium tracking-tight")
EmptyDescription = ClassVariants.build(base: "text-muted-foreground text-sm/relaxed ...")
EmptyActions     = ClassVariants.build(base: "flex flex-wrap items-center justify-center gap-2")
EmptyContent     = ClassVariants.build(base: "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance")

Accessibility

Empty renders as a <div> with data-slot="empty". Sub-parts use data-slot attributes (e.g., data-slot="empty-header").

For semantic meaning, use component_options to set a role:

<%= kui(:empty, role: :status, "aria-label": "No results") do %>
  ...
<% end %>