Empty State

A placeholder panel for where a list, table or search result would otherwise render nothing.

Key Features

  • Four variants:dashed (default), :outline, :filled, :plain
  • Tinted icon tile — any Lucide icon, any RapidRailsUI colour
  • Two slotsmedia for illustrations, action for buttons
  • Announces itselfrole="status" aria-live="polite"
  • No JavaScript — pure server-rendered HTML
  • Starter tier — available on all RRUI licenses

Basic Usage

Both title: and description: are optional. A bare rui_empty_state renders the panel alone.

No users found

Try adjusting your search filters

Basic Usage
<%= rui_empty_state(
  title: "No users found",
  description: "Try adjusting your search filters"
) %>

With an Icon

The icon renders in a soft-tinted round tile. color: accepts any RapidRailsUI colour and tints the tile only.

No projects yet

Create your first project to get started

Nothing to review

You're all caught up

With an Icon
<%= rui_empty_state(
  title: "No projects yet",
  description: "Create your first project to get started",
  icon: :folder_plus,
  color: :primary
) %>

With Actions

The action slot accepts many entries. They wrap, and follow the panel's alignment.

No results

Nothing matched that search.

With Actions
<%= rui_empty_state(
  title: "No results",
  description: "Nothing matched that search.",
  icon: :search_x
) do |empty| %>
  <% empty.with_action { rui_button("Clear filters", color: :primary) } %>
  <% empty.with_action { rui_button("Browse all", variant: :outline) } %>
<% end %>

Inside a Table

rui_table's with_empty_state slot is the most common home for this. rui_live_search has the same slot.

NameEmail

No users found

Try adjusting your search filters

Inside a Table
<%= rui_table(collection: @users) do |table| %>
  <% table.with_column(key: :name, label: "Name") %>
  <% table.with_column(key: :email, label: "Email") %>

  <% table.with_empty_state do %>
    <%= rui_empty_state(
      title: "No users found",
      description: "Try adjusting your search filters",
      icon: :users,
      variant: :plain
    ) %>
  <% end %>
<% end %>

Variants

Pick by what surrounds the panel: :dashed on a bare page, :outline or :plain inside something that already has chrome.

:dashed — Dashed border, transparent fill

Nothing here

:outline — Solid hairline on a solid surface

Nothing here

:filled — No border, tinted fill

Nothing here

:plain — No border, no fill

Nothing here

Variants
<%= rui_empty_state(title: "Nothing here", variant: :outline) %>

Sizes

Size drives container padding, icon tile size and the title/description type scale together.

:sm

No invoices

Invoices appear here within 24 hours of a sale.

:base

No invoices

Invoices appear here within 24 hours of a sale.

:lg

No invoices

Invoices appear here within 24 hours of a sale.

Sizes
<%= rui_empty_state(title: "No invoices", size: :lg) %>

Alignment

Actions follow the alignment, so a start-aligned panel does not centre its buttons.

:center

No activity

Nothing has happened yet.

:start

No activity

Nothing has happened yet.

Alignment
<%= rui_empty_state(title: "No activity", align: :start) %>

Custom Media

The media slot replaces the icon tile entirely. If you pass both icon: and a media slot, the slot wins — you never get two.

AN RB TC

No team members

Invite someone to get started

Custom Media
<%= rui_empty_state(title: "No team members") do |empty| %>
  <% empty.with_media do %>
    <%= image_tag "illustrations/empty-team.svg", class: "w-40" %>
  <% end %>
<% end %>

Accessibility

The container renders role="status" and aria-live="polite". An empty state usually appears because a filter or search changed, and a screen reader user needs to be told the result set is now empty.

Both are overridable if you are placing the panel somewhere static rather than in response to a user action.

Overriding the live region
<%= rui_empty_state(title: "No data", role: "region", "aria-live": "off") %>