PhoenixPaper.Tabs (PhoenixPaper v0.1.0)

Copy Markdown View Source

A tablist container (pp_tabs/1), in the spirit of MUI's Tabs — composed with PhoenixPaper.Tab (a clickable tab) and PhoenixPaper.TabPanel (its content, usually written after the whole pp_tabs/1 block, not nested inside it, same as MUI):

<.pp_tabs id="demo-tabs">
  <.pp_tab id="demo-tabs" value="one" default_selected>One</.pp_tab>
  <.pp_tab id="demo-tabs" value="two">Two</.pp_tab>
  <.pp_tab id="demo-tabs" value="three">Three</.pp_tab>
</.pp_tabs>

<.pp_tab_panel id="demo-tabs" value="one" default_selected>Content one</.pp_tab_panel>
<.pp_tab_panel id="demo-tabs" value="two">Content two</.pp_tab_panel>
<.pp_tab_panel id="demo-tabs" value="three">Content three</.pp_tab_panel>

Every pp_tab/1/pp_tab_panel/1 in a group needs the same id as the pp_tabs/1 they belong to (the same requirement, for the same reason, as PhoenixPaper.Accordion's shared id) — it's how select/2,3 below builds the selectors that flip everything together. Each pp_tab/1's value must be unique within that group and match exactly one pp_tab_panel/1's value.

Switching tabs is handled entirely client-side with Phoenix.LiveView.JS commands (add_class/remove_class/set_attribute/show/hide) fired on click — no server round-trip, no LiveView assign to fight with on the next unrelated re-render, the same approach PhoenixPaper.Dialog/ PhoenixPaper.Drawer use for their own show/hide, just extended here to an exclusive N-way choice instead of a boolean. This is also why Tabs isn't built the checkbox/radio-plus-peer-checked: way PhoenixPaper.Accordion is: peer-checked:/has-* can only express "is some sibling checked", not "which specific one of N siblings is checked" — and the panel that needs to react usually isn't even a DOM sibling of the tabs at all (see "CSS-only interactive state" in AGENTS.md). Mapping a selected tab to its one matching panel needs real per-element targeting, which only JS commands (or a full LiveView assign) give you.

There's no moving/sliding indicator animation like MUI's — that requires measuring a specific tab's pixel offset/width at runtime, a genuine client-side layout query that Phoenix.LiveView.JS (which only issues fixed DOM commands, never custom computed logic) can't do without a bespoke JS hook. Instead the selected tab styles itself (colored text + border) — visually simpler than MUI's sliding underline, but zero custom JS. There's also no roving tabindex (MUI's Tabs puts only the selected tab in the normal Tab order, -1 on the rest) — every tab stays normally focusable here, a small deviation from strict ARIA authoring practice traded for not needing JS to manage focus state too.

Like PhoenixPaper.ButtonGroup, there's no group-level color that cascades from Tabs down to every Tab — HEEx has no mechanism for a parent component to reach into a child component's own assigns, so color is set per-Tab (keep it consistent across a group yourself).

Summary

Functions

Renders a tablist container. See the module doc.

A Phoenix.LiveView.JS command that selects the tab/panel value within the id group: deselects every other tab (stripping its active-indicator classes and aria-selected) and hides every other panel, then adds the active classes for color to this tab, marks it aria-selected="true", and shows its matching panel. Wired automatically to every pp_tab/1's own click — you don't normally call this yourself, but it's public so a trigger elsewhere on the page (e.g. a "next tab" button) can also switch tabs the same way PhoenixPaper.Dialog.show/2 lets any button open a dialog.

Functions

pp_tabs(assigns)

Renders a tablist container. See the module doc.

Attributes

  • id (:string) (required) - shared with every Tab/TabPanel in the group.
  • orientation (:string) - Defaults to "horizontal". Must be one of "horizontal", or "vertical".
  • variant (:string) - scrollable/full_width only affect orientation="horizontal". Defaults to "standard". Must be one of "standard", "scrollable", or "full_width".
  • centered (:boolean) - ignored for variant="scrollable"/"full_width" or orientation="vertical". Defaults to false.
  • paperize (:boolean) - Defaults to true.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted. Supports all globals plus: ["aria-label", "aria-labelledby"].

Slots

  • inner_block (required)

select(id, value, color \\ "primary")

@spec select(String.t(), String.t(), String.t()) :: Phoenix.LiveView.JS.t()

A Phoenix.LiveView.JS command that selects the tab/panel value within the id group: deselects every other tab (stripping its active-indicator classes and aria-selected) and hides every other panel, then adds the active classes for color to this tab, marks it aria-selected="true", and shows its matching panel. Wired automatically to every pp_tab/1's own click — you don't normally call this yourself, but it's public so a trigger elsewhere on the page (e.g. a "next tab" button) can also switch tabs the same way PhoenixPaper.Dialog.show/2 lets any button open a dialog.