Tabs

Organises content into distinct panels, showing one at a time via a tab strip.

Installation

bin/rails g shadcnrb:component tabs

Usage

Each t.tab is one trigger + panel pair — the tab strip is assembled from them, and the pairing is structural, no value: ids to match up. The first tab starts active unless one passes active: true.

Manage your account details and preferences.

<%= sui.tabs do |t| %>
  <%= t.tab "Account" do %>
    <p class="text-sm text-muted-foreground pt-2">Manage your account details and preferences.</p>
  <% end %>
  <%= t.tab "Password" do %>
    <p class="text-sm text-muted-foreground pt-2">Change your password and security settings.</p>
  <% end %>
  <%= t.tab "Notifications" do %>
    <p class="text-sm text-muted-foreground pt-2">Choose what notifications you receive.</p>
  <% end %>
<% end %>

With richer content

Tab content can hold any markup — forms, cards, lists, etc.

Total revenue

$45,231.89

+20.1% from last month

<%= sui.tabs do |t| %>
  <%= t.tab "Overview" do %>
    <div class="pt-2 space-y-1">
      <p class="text-sm font-medium">Total revenue</p>
      <p class="text-2xl font-bold">$45,231.89</p>
      <p class="text-xs text-muted-foreground">+20.1% from last month</p>
    </div>
  <% end %>
  <%= t.tab "Activity" do %>
    <div class="pt-2 space-y-1">
      <p class="text-sm font-medium">Recent sign-ins</p>
      <p class="text-xs text-muted-foreground">Chrome · macOS · 2 minutes ago</p>
      <p class="text-xs text-muted-foreground">Safari · iOS · 1 hour ago</p>
    </div>
  <% end %>
<% end %>

Detached strip

list: false renders the root and panels without a strip, and sui.tabs_strip renders only the strip — anywhere on the page, such as a card header next to the title. The two share the root's id; the strip's triggers carry data-tabs and the controller keeps their state in sync. names repeats the tab labels in order, and variant: should match the root's.

Revenue by month

<div class="w-full rounded-xl border">
  <div class="flex items-center justify-between border-b p-4">
    <h3 class="text-sm font-medium">Revenue by month</h3>
    <%= sui.tabs_strip tabs: "revenue-tabs", names: %w[Chart Data],
          class: "h-8 p-0.5", trigger: { class: "px-2 py-0.5 text-xs" } %>
  </div>
  <div class="p-4">
    <%= sui.tabs id: "revenue-tabs", list: false do |t| %>
      <%= t.tab "Chart" do %>
        <div class="flex h-24 items-end gap-1">
          <% [40, 70, 55, 90, 65, 80].each do |h| %>
            <div class="flex-1 rounded-t bg-primary/70" style="height: <%= h %>%"></div>
          <% end %>
        </div>
      <% end %>
      <%= t.tab "Data" do %>
        <p class="text-sm text-muted-foreground">Jan 40 · Feb 70 · Mar 55 · Apr 90 · May 65 · Jun 80</p>
      <% end %>
    <% end %>
  </div>
</div>

API

MethodArgsDefaultDescription
sui.tabsvariant:, orientation:, list:, content:, **opts:default / :horizontalRoot container. Mounts the shadcnrb--tabs--component controller, assembles the tab strip (role="tablist") and panels from the block's t.tab calls. list: / content: are option hashes for the strip and every panel.
t.tab(name)active:, **opts, &blockfalseOne trigger + panel pair: name is the tab button, the block is its panel. Inactive panels render hidden and stay in the DOM. **opts land on the tab button.
sui.tabs_striptabs:, names:, variant:, trigger:, **opts:defaultStrip only, for a list: false root with that id. trigger: applies to every trigger; other options go to the list. A strip for a root that isn't on the page is a no-op.