Tabs

Component

Interactive examples and API documentation

Basic
Default activate first tab.
Content of Tab Pane 1
Code
<%= render Hakumi::Tabs::Component.new do |tabs| %>
  <% tabs.with_item(key: "1", label: "Tab 1") do %>
    Content of Tab Pane 1
  <% end %>
  <% tabs.with_item(key: "2", label: "Tab 2") do %>
    Content of Tab Pane 2
  <% end %>
  <% tabs.with_item(key: "3", label: "Tab 3") do %>
    Content of Tab Pane 3
  <% end %>
<% end %>
Disabled
Disabled a tab.
Content of Tab Pane 1
Code
<%= render Hakumi::Tabs::Component.new(default_active_key: "1") do |tabs| %>
  <% tabs.with_item(key: "1", label: "Tab 1") do %>
    Content of Tab Pane 1
  <% end %>
  <% tabs.with_item(key: "2", label: "Tab 2", disabled: true) do %>
    Content of Tab Pane 2
  <% end %>
  <% tabs.with_item(key: "3", label: "Tab 3") do %>
    Content of Tab Pane 3
  <% end %>
<% end %>
Centered
Centered tabs.
Content of Tab Pane 1
Code
<%= render Hakumi::Tabs::Component.new(centered: true) do |tabs| %>
  <% tabs.with_item(key: "1", label: "Tab 1") do %>
    Content of Tab Pane 1
  <% end %>
  <% tabs.with_item(key: "2", label: "Tab 2") do %>
    Content of Tab Pane 2
  <% end %>
  <% tabs.with_item(key: "3", label: "Tab 3") do %>
    Content of Tab Pane 3
  <% end %>
<% end %>
Icon
The Tab with Icon.
Content of Tab Pane 1
Code
<%= render Hakumi::Tabs::Component.new do |tabs| %>
  <% tabs.with_item(key: "1", label: "Tab 1", icon: :home) do %>
    Content of Tab Pane 1
  <% end %>
  <% tabs.with_item(key: "2", label: "Tab 2", icon: :setting) do %>
    Content of Tab Pane 2
  <% end %>
<% end %>
Indicator
Set indicator prop to custom indicator size and align.
Indicator size: 10px, align: center (default)
Content of Tab Pane 1
Indicator size: 10px, align: start
Content of Tab Pane 1
Indicator size: 10px, align: end
Content of Tab Pane 1
Code
<%= render Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Indicator size: 10px, align: center (default)
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new(indicator: { size: 10, align: :center }) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Indicator size: 10px, align: start
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new(indicator: { size: 10, align: :start }) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Indicator size: 10px, align: end
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new(indicator: { size: 10, align: :end }) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>
<% end %>
Slide
In order to fit in more tabs, they can slide left and right (or up and down).
Content of Tab Pane 1
Content of Tab Pane 1
Code
<%= render Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new do |tabs| %>
      <% (1..30).each do |i| %>
        <% tabs.with_item(key: i.to_s, label: "Tab #{i}") do %>
          Content of Tab Pane <%= i %>
        <% end %>
      <% end %>
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Divider::Component.new %>
  <% end %>
  <% space.with_item do %>
    <div style="height: 300px;">
      <%= render Hakumi::Tabs::Component.new(tab_position: :left) do |tabs| %>
        <% (1..30).each do |i| %>
          <% tabs.with_item(key: i.to_s, label: "Tab #{i}") do %>
            Content of Tab Pane <%= i %>
          <% end %>
        <% end %>
      <% end %>
    </div>
  <% end %>
<% end %>
Extra content
You can add extra actions to the right or left or even both side of Tabs.
Content of Tab Pane 1
Content of Tab Pane 1
Code
<% extra_button = capture do %>
  <%= render Hakumi::Button::Component.new(size: :small) do %>Extra Action<% end %>
<% end %>

<% left_button = capture do %>
  <%= render Hakumi::Button::Component.new(size: :small) do %>Left<% end %>
<% end %>

<% right_button = capture do %>
  <%= render Hakumi::Button::Component.new(size: :small) do %>Right<% end %>
<% end %>

<%= render Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new(tab_bar_extra_content_right: extra_button) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new(
      tab_bar_extra_content_left: left_button,
      tab_bar_extra_content_right: right_button
    ) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
    <% end %>
  <% end %>
<% end %>
Size
Large size tabs are usually used in page header, and small size could be used in Modal.
Size: large
Content of Tab Pane 1
Size: middle (default)
Content of Tab Pane 1
Size: small
Content of Tab Pane 1
Code
<%= render Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Size: large
    <% end %>
    <%= render Hakumi::Tabs::Component.new(size: :large) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Size: middle (default)
    <% end %>
    <%= render Hakumi::Tabs::Component.new(size: :middle) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Size: small
    <% end %>
    <%= render Hakumi::Tabs::Component.new(size: :small) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>
<% end %>
Placement
Tab's placement: top, bottom, left or right.
Tab position: top (default)
Content of Tab Pane 1
Tab position: bottom
Content of Tab Pane 1
Tab position: left
Content of Tab Pane 1
Tab position: right
Content of Tab Pane 1
Code
<%= render Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Tab position: top (default)
    <% end %>
    <%= render Hakumi::Tabs::Component.new(tab_position: :top) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Divider::Component.new %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Tab position: bottom
    <% end %>
    <%= render Hakumi::Tabs::Component.new(tab_position: :bottom) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
      <% tabs.with_item(key: "3", label: "Tab 3") do %>
        Content of Tab Pane 3
      <% end %>
    <% end %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Divider::Component.new %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Tab position: left
    <% end %>
    <div style="height: 200px; width: 100%;">
      <%= render Hakumi::Tabs::Component.new(tab_position: :left) do |tabs| %>
        <% tabs.with_item(key: "1", label: "Tab 1") do %>
          Content of Tab Pane 1
        <% end %>
        <% tabs.with_item(key: "2", label: "Tab 2") do %>
          Content of Tab Pane 2
        <% end %>
        <% tabs.with_item(key: "3", label: "Tab 3") do %>
          Content of Tab Pane 3
        <% end %>
      <% end %>
    </div>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Divider::Component.new %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Typography::Text::Component.new(type: :secondary) do %>
      Tab position: right
    <% end %>
    <div style="height: 200px; width: 100%;">
      <%= render Hakumi::Tabs::Component.new(tab_position: :right) do |tabs| %>
        <% tabs.with_item(key: "1", label: "Tab 1") do %>
          Content of Tab Pane 1
        <% end %>
        <% tabs.with_item(key: "2", label: "Tab 2") do %>
          Content of Tab Pane 2
        <% end %>
        <% tabs.with_item(key: "3", label: "Tab 3") do %>
          Content of Tab Pane 3
        <% end %>
      <% end %>
    </div>
  <% end %>
<% end %>
Card type tab
Another type of Tabs, which doesn't support vertical mode.
Content of Tab Pane 1
Code
<%= render Hakumi::Tabs::Component.new(type: :card) do |tabs| %>
  <% tabs.with_item(key: "1", label: "Tab 1") do %>
    Content of Tab Pane 1
  <% end %>
  <% tabs.with_item(key: "2", label: "Tab 2") do %>
    Content of Tab Pane 2
  <% end %>
  <% tabs.with_item(key: "3", label: "Tab 3") do %>
    Content of Tab Pane 3
  <% end %>
<% end %>
Add & close tab
Only card type Tabs support adding & closable. Use closable: false to disable close.
Content of Tab Pane 1
Code
<%= render Hakumi::Tabs::Component.new(
  type: :editable_card,
  default_active_key: "1",
  id: "editable-tabs"
) do |tabs| %>
  <% tabs.with_item(key: "1", label: "Tab 1") do %>
    Content of Tab Pane 1
  <% end %>
  <% tabs.with_item(key: "2", label: "Tab 2") do %>
    Content of Tab Pane 2
  <% end %>
  <% tabs.with_item(key: "3", label: "Tab 3", closable: false) do %>
    Content of Tab Pane 3 (Not closable)
  <% end %>
<% end %>

<script>
  (function() {
    const tabsEl = document.getElementById("editable-tabs");
    if (!tabsEl || tabsEl._editableInitialized) return;
    tabsEl._editableInitialized = true;

    let tabIndex = 4;

    // Handle add button click
    tabsEl.addEventListener("hakumi--tabs:add", function(e) {
      const tabs = tabsEl.hakumiComponent.api;
      if (tabs) {
        tabs.addTab({
          key: "tab-" + tabIndex,
          label: "New Tab " + tabIndex,
          content: "Content of new tab " + tabIndex,
          closable: true
        });
        tabIndex++;
      }
    });

    // Handle tab removal (optional - for logging/confirmation)
    tabsEl.addEventListener("hakumi--tabs:edit", function(e) {
      console.log("Tab removed:", e.detail.key);
    });
  })();
</script>
Customized trigger of new tab
Hide default plus icon, and bind event for customized trigger.
Content of Tab Pane 1
Code
<%= render Hakumi::Space::Component.new(direction: :vertical, block: true) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Button::Component.new(
      type: :primary,
      id: "custom-add-btn"
    ) do %>
      Add Custom Tab
    <% end %>
  <% end %>
  <% space.with_item do %>
    <%= render Hakumi::Tabs::Component.new(
      type: :editable_card,
      hide_add: true,
      default_active_key: "1",
      id: "custom-add-tabs"
    ) do |tabs| %>
      <% tabs.with_item(key: "1", label: "Tab 1") do %>
        Content of Tab Pane 1
      <% end %>
      <% tabs.with_item(key: "2", label: "Tab 2") do %>
        Content of Tab Pane 2
      <% end %>
    <% end %>
  <% end %>
<% end %>

<script>
  (function() {
    const tabsEl = document.getElementById("custom-add-tabs");
    const addBtn = document.getElementById("custom-add-btn");
    if (!tabsEl || !addBtn || addBtn._customAddInitialized) return;
    addBtn._customAddInitialized = true;

    let tabIndex = 3;

    addBtn.addEventListener("click", function() {
      if (tabsEl.hakumiComponent.api) {
        tabsEl.hakumiComponent.api.addTab({
          key: "custom-" + tabIndex,
          label: "Custom Tab " + tabIndex,
          content: "Content of custom tab " + tabIndex,
          closable: true
        });
        tabIndex++;
      }
    });
  })();
</script>

Tabs API

Prop Type Default Description
active_key String nil Current active tab key (controlled mode).
default_active_key String nil Initial active tab key.
type Symbol :line Type of tabs. Options: :line, :card, :editable_card.
size Symbol :middle Size of tabs. Options: :large, :middle, :small.
tab_position Symbol :top Position of tabs. Options: :top, :bottom, :left, :right.
centered Boolean false Center the tabs.
animated Boolean true Whether to animate tab transitions.
indicator Hash {} Custom indicator config. Keys: size (Integer), align (:start, :center, :end).
tab_bar_gutter Integer nil Gap between tabs in pixels.
tab_bar_extra_content_left String/Component nil Extra content on the left side of tab bar.
tab_bar_extra_content_right String/Component nil Extra content on the right side of tab bar.
add_icon String/Component nil Custom add icon for editable_card type.
hide_add Boolean false Hide add button for editable_card type.
destroy_inactive_tab_pane Boolean false Destroy inactive tab panes for performance.

Tabs.Item API

Prop Type Default Description
key String - Unique key for the tab (required).
label String nil Tab label text. Defaults to key if not provided.
icon Symbol nil Icon name to display before label.
disabled Boolean false Disable the tab.
closable Boolean type dependent Show close button. Defaults to true for editable_card type.
close_icon Symbol :close Custom close icon.
force_render Boolean false Force render tab pane even when not active.

JavaScript API (element.hakumiTabs)

Prop Type Default Description
getActiveKey() Function - Get current active tab key.
getValue() Function - Alias for getActiveKey().
setActiveKey(key) Function - Set active tab by key.
setValue(key) Function - Alias for setActiveKey().
getItems() Function - Return all tabs with { key, label, disabled, closable, active }.
next() Function - Navigate to the next tab.
prev() Function - Navigate to the previous tab.
reset() Function - Activate the first available tab.
removeTab(key) Function - Remove a tab by key (dispatches hakumi--tabs:edit).
addTab(options = {}) Function - Create a tab dynamically. Options: key, label, content, closable (default true), activate (default true). Returns the tab key.

Events

Prop Type Default Description
hakumi--tabs:change CustomEvent - Triggered when active tab changes. Detail: { activeKey, previousKey }.
hakumi--tabs:edit CustomEvent - Triggered when a tab is removed. Detail: { type: 'remove', key }.
hakumi--tabs:add CustomEvent - Triggered when the builtin add button is clicked. Detail: { action: 'add' }. Use it to call the public API.
hakumi--tabs:tabAdded CustomEvent - Triggered after a tab was successfully added via API. Detail: { key, label }.