Components Tabs

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Tabs

Three styles: segmented (a raised pill on a muted track), underline, and pill. Tabs are links or buttons - wire them to live_patch, JS commands or events.
Three styles

segmented is the modern default for in-page switching - the active tab is a raised pill on a muted track, nested-radius; underline suits page-level navigation; pill is the roomy classic. Tabs are links or buttons via link_type - wire them to live_patch, JS commands or events; number renders the count badge.

heex
<div class="flex flex-col items-center gap-8">
<.tabs variant="segmented">
<.tab variant="segmented" is_active link_type="button">Overview</.tab>
<.tab variant="segmented" link_type="button">Analytics</.tab>
<.tab variant="segmented" link_type="button" number={4}>Reports</.tab>
</.tabs>
<.tabs variant="underline">
<.tab variant="underline" is_active link_type="button">Overview</.tab>
<.tab variant="underline" link_type="button">Analytics</.tab>
<.tab variant="underline" link_type="button" number={4}>Reports</.tab>
</.tabs>
<.tabs variant="pill">
<.tab variant="pill" is_active link_type="button">Overview</.tab>
<.tab variant="pill" link_type="button">Analytics</.tab>
<.tab variant="pill" link_type="button" number={4}>Reports</.tab>
</.tabs>
</div>
With icons

Anything goes in the tab body - icons beside labels here. The legacy underline flag still works; variant wins when both are set.

heex
<.tabs variant="segmented">
<.tab variant="segmented" is_active link_type="button">
<.icon name="hero-chart-bar" class="w-4 h-4 mr-1.5" /> Dashboard
</.tab>
<.tab variant="segmented" link_type="button">
<.icon name="hero-users" class="w-4 h-4 mr-1.5" /> Team
</.tab>
<.tab variant="segmented" link_type="button">
<.icon name="hero-cog-6-tooth" class="w-4 h-4 mr-1.5" /> Settings
</.tab>
</.tabs>
Properties
<.tabs>
Attribute Type Default Description
class any nil CSS class
rest global
underline boolean false underlines your tabs (legacy for variant="underline")
variant string nil tab style: pill (default), underline, or segmented - active tab is a raised white pill on a muted track. Overrides the legacy underline flag
one of: nil, "pill", "underline", "segmented"
:inner_block slot slot
<.tab>
Attribute Type Default Description
class any nil CSS class
disabled boolean false disables your tab
is_active boolean false indicates the current tab
label string nil labels your tab
link_type string "a" button renders a real <button type="button"> for phx-click / JS-command tabs - no href, and no implicit submit inside forms
one of: "a", "live_patch", "live_redirect", "button"
number integer nil indicates a number next to your tab
on_change {:struct, Phoenix.LiveView.JS} %Phoenix.LiveView.JS{ops: []} JS commands to run when this tab is selected
rest global
to string nil link path
underline boolean false underlines your tab (legacy for variant="underline")
variant string nil match the parent tabs variant. Overrides the legacy underline flag
one of: nil, "pill", "underline", "segmented"
:inner_block slot slot