Tabs
v0.0.10
mix mishka.ui.gen.headless tabs
Overview of the Phoenix and LiveView Headless Tabs Component
The Mishka Chelekom Headless Tabs is a set of tabs for Phoenix LiveView with no styling of its own. It renders the semantic markup with the full WAI-ARIA tablist wiring, roving focus, and an indicator that slides and resizes under the active tab through CSS variables. Every part takes a class attribute: class for the root, list_class for the tablist, tab_class for every tab, indicator_class for the indicator, panels_class for the wrapper around the panels and panel_class for every panel.
A :tab or :panel slot also takes its own class, for that one tab or panel. Both apply to the element, the attribute's classes first and the slot's after them.
Base UI Tabs example (Phoenix headless)
Base UI Tabs example (Phoenix headless)
Workspace stats and activity.
Milestones and deadlines.
Profile and preferences.
<.tabs
id="baseui-tabs-hero"
default_value="overview"
class="w-full max-w-xs"
list_class="relative z-1 -mb-px flex gap-1"
indicator_class="absolute top-0 left-0 -z-1 h-full w-(--active-tab-width) translate-x-(--active-tab-left) border-x border-t border-neutral-950 bg-white transition-[translate,width] duration-150 ease-in-out dark:border-white dark:bg-neutral-950"
panels_class="grid w-full min-h-32 grid-cols-1 border border-neutral-950 dark:border-white"
tab_class="flex h-[calc(2rem+1px)] items-center justify-center bg-transparent px-2 py-0 font-inherit text-sm font-normal leading-5 break-keep whitespace-nowrap text-neutral-600 outline-none select-none hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[active]:text-neutral-950 dark:text-neutral-300 dark:hover:text-white dark:data-[active]:text-white"
panel_class="col-start-1 row-start-1 flex w-full items-center justify-center bg-white p-4 text-center text-sm text-neutral-950 outline-none focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:bg-neutral-950 dark:text-white [&[hidden]]:hidden"
>
<:tab value="overview">Overview</:tab>
<:tab value="projects">Projects</:tab>
<:tab value="account">Account</:tab>
<:panel value="overview"><p>Workspace stats and activity.</p></:panel>
<:panel value="projects"><p>Milestones and deadlines.</p></:panel>
<:panel value="account"><p>Profile and preferences.</p></:panel>
</.tabs>Tabs example - vertical with a disabled tab (Phoenix headless)
With orientation="vertical" the arrow keys are Up and Down, and the indicator is sized from --active-tab-top and --active-tab-height. A tab with disabled is skipped by the arrow keys and carries data-disabled, which the classes below dim.
Tabs example - vertical with a disabled tab (Phoenix headless)
Name, language and time zone.
Invite people and set their roles.
Plans and invoices.
<.tabs
id="tabs-vertical"
orientation="vertical"
default_value="general"
class="flex w-full max-w-sm gap-4"
list_class="relative flex shrink-0 flex-col border-s border-neutral-300 dark:border-neutral-700"
indicator_class="absolute -start-px top-0 h-(--active-tab-height) w-0.5 translate-y-(--active-tab-top) bg-neutral-950 transition-[translate,height] duration-150 ease-in-out dark:bg-white"
tab_class="flex h-9 items-center px-3 text-start text-sm text-neutral-600 outline-none select-none hover:not-data-[disabled]:text-neutral-950 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 data-[active]:text-neutral-950 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-40 dark:text-neutral-300 dark:hover:not-data-[disabled]:text-white dark:focus-visible:outline-white dark:data-[active]:text-white"
panels_class="min-w-0 flex-1"
panel_class="py-2 text-sm text-neutral-950 outline-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:text-white dark:focus-visible:outline-white"
>
<:tab value="general">General</:tab>
<:tab value="members">Members</:tab>
<:tab value="billing" disabled>Billing</:tab>
<:panel value="general"><p>Name, language and time zone.</p></:panel>
<:panel value="members"><p>Invite people and set their roles.</p></:panel>
<:panel value="billing"><p>Plans and invoices.</p></:panel>
</.tabs>Attributes and slots
id is required. default_value is the tab that is active on the first render, and value is the controlled active tab: when the server changes it, the hook moves to that tab without pushing an event. With neither, the first enabled tab is active. orientation is "horizontal" (the default) or "vertical", activate_on_focus is on by default, and indicator={false} leaves the indicator out.
on_change is a LiveView event pushed with %{"value" => value} when a person switches tab. It goes to the LiveView or LiveComponent that renders the tabs, or to the element on_change_target names.
A :tab takes value, disabled and class. A :panel takes value and class. A tab and a panel are matched by value; without one, a tab or panel takes <id>-<position> as its value, so they match by position.
Parts & data attributes
Each part carries a stable data-part and a structural chelekom-tabs__* class you can target, plus state attributes that drive the active, hidden and orientation styling and the sliding indicator.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | data-orientation, data-activation-direction | Carries the Tabs hook; data-value is the controlled active tab; takes class. |
| Tablist | tablist | role="tablist", aria-orientation | Exposes the --active-tab-left/right/top/bottom/width/height CSS variables; takes list_class. |
| Tab | tab | role="tab", aria-selected, aria-controls, data-active, data-disabled, data-orientation | An individual tab button, part of the roving tabindex; takes tab_class for every tab and the slot's class for one. |
| Indicator | indicator | aria-hidden, data-orientation, data-activation-direction | The animated marker; positions itself from the --active-tab-* vars; takes indicator_class. |
| Panels | panels | - | Wraps every panel; takes panels_class. |
| Panel | panel | role="tabpanel", aria-labelledby, hidden, data-hidden, data-index, data-orientation | The content matched to its tab by value or position; takes panel_class for every panel and the slot's class for one. |
Accessibility
Follows the WAI-ARIA Tabs pattern: arrow keys move focus along the tablist's orientation, and Home / End jump to the first / last tab. Activation follows focus automatically unless activate_on_focus is turned off, in which case Enter or Space selects the focused tab.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | — |
value
|
string
| — | — | No | Active tab value (controlled by the server) |
default_value
|
string
| — | — | No | Initial active tab value (uncontrolled) |
orientation
|
string
|
horizontal
|
horizontalvertical
| No | — |
activate_on_focus
|
boolean
|
true
| — | No | Activate a tab when it receives focus (automatic) |
indicator
|
boolean
|
true
| — | No | Render the animated indicator element |
on_change
|
string
| — | — | No | LiveView event pushed on user switch ({value}) |
on_change_target
|
string
| — | — | No | Optional pushEventTo target (e.g. a LiveComponent selector) |
class
|
any
| — | — | No | — |
list_class
|
any
| — | — | No | — |
indicator_class
|
any
| — | — | No | — |
panels_class
|
any
| — | — | No | — |
tab_class
|
any
| — | — | No | — |
panel_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
tab
| Yes | A tab label |
panel
| Yes | A tab panel (matched to its tab by value/position) |
Attributes of the tab slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
value
|
string
| — | — | No | Stable value (defaults to the positional index) |
disabled
|
boolean
| — | — | No | — |
class
|
any
| — | — | No | — |
Attributes of the panel slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
value
|
string
| — | — | No | — |
class
|
any
| — | — | No | — |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
tabs
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless tabs