Basic
Default activate first tab.
Tab 1
Tab 2
Tab 3
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.
Tab 1
Tab 2
Tab 3
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.
Tab 1
Tab 2
Tab 3
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.
Tab 1
Tab 2
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)
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Indicator size: 10px, align: start
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Indicator size: 10px, align: end
Tab 1
Tab 2
Tab 3
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).
Tab 1
Tab 2
Tab 3
Tab 4
Tab 5
Tab 6
Tab 7
Tab 8
Tab 9
Tab 10
Tab 11
Tab 12
Tab 13
Tab 14
Tab 15
Tab 16
Tab 17
Tab 18
Tab 19
Tab 20
Tab 21
Tab 22
Tab 23
Tab 24
Tab 25
Tab 26
Tab 27
Tab 28
Tab 29
Tab 30
Content of Tab Pane 1
Tab 1
Tab 2
Tab 3
Tab 4
Tab 5
Tab 6
Tab 7
Tab 8
Tab 9
Tab 10
Tab 11
Tab 12
Tab 13
Tab 14
Tab 15
Tab 16
Tab 17
Tab 18
Tab 19
Tab 20
Tab 21
Tab 22
Tab 23
Tab 24
Tab 25
Tab 26
Tab 27
Tab 28
Tab 29
Tab 30
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.
Tab 1
Tab 2
Content of Tab Pane 1
Tab 1
Tab 2
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
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Size: middle (default)
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Size: small
Tab 1
Tab 2
Tab 3
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)
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab position: bottom
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab position: left
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab position: right
Tab 1
Tab 2
Tab 3
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.
Tab 1
Tab 2
Tab 3
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.
Tab 1
Tab 2
Tab 3
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.
Tab 1
Tab 2
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 }. |