Corex. Accordion
(Corex v0.1.0-rc.1)
View Source
Phoenix implementation of the Zag.js Accordion.
Anatomy
Minimal
<.accordion
class="accordion"
items={
Corex.Content.new([
%{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
%{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
%{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
])
}
/>With slots
With items and <:indicator> slot so every item shares the same indicator markup.
<.accordion
class="accordion"
items={
Corex.Content.new([
%{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
%{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
%{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
])
}
>
<:indicator>
<.heroicon name="hero-chevron-right" />
</:indicator>
</.accordion>Custom slots
With items, customize each item using slots with :let={item} to access the item and its meta data
<.accordion
class="accordion"
value="lorem"
items={
Corex.Content.new([
%{
value: "lorem",
label: "Lorem ipsum dolor sit amet",
content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique.",
meta: %{indicator: "hero-arrow-long-right", icon: "hero-chat-bubble-left-right"}
},
%{
label: "Duis dictum gravida odio ac pharetra?",
content: "Nullam eget vestibulum ligula, at interdum tellus.",
meta: %{indicator: "hero-chevron-right", icon: "hero-device-phone-mobile"}
},
%{
value: "donec",
label: "Donec condimentum ex mi",
content: "Congue molestie ipsum gravida a. Sed ac eros luctus.",
disabled: true,
meta: %{indicator: "hero-chevron-double-right", icon: "hero-phone"}
}
])
}
>
<:trigger :let={item}>
<.heroicon name={item.meta.icon} />{item.label}
</:trigger>
<:content :let={item}><p>{item.content}</p></:content>
<:indicator :let={item}>
<.heroicon name={item.meta.indicator} />
</:indicator>
</.accordion>Manual slots
With an empty items list, use multiple :trigger, :content, and optional :indicator slots.
Each slot takes a value string that ties the three together.
<.accordion class="accordion" value="lorem">
<:trigger value="lorem">
<.heroicon name="hero-chevron-right" /> Lorem ipsum dolor sit amet
</:trigger>
<:content value="lorem"><p>Consectetur adipiscing elit. Sed sodales ullamcorper tristique.</p></:content>
<:indicator value="lorem">
<.heroicon name="hero-chevron-down" />
</:indicator>
<:trigger value="duis">
<.heroicon name="hero-chevron-right" /> Duis dictum gravida odio ac pharetra?
</:trigger>
<:content value="duis"><p>Nullam eget vestibulum ligula, at interdum tellus.</p></:content>
<:indicator value="duis">
<.heroicon name="hero-chevron-down" />
</:indicator>
</.accordion>Compound
Take full structural control with the accordion_root, accordion_item, accordion_trigger, accordion_content, and accordion_indicator sub-components.
Manual items
<.accordion :let={ctx} compound class="accordion">
<.accordion_root ctx={ctx}>
<.accordion_item :let={item} ctx={ctx} value="lorem">
<.accordion_trigger item={item}>
Lorem ipsum dolor sit amet
<:indicator>
<.accordion_indicator item={item}>
<.heroicon name="hero-chevron-right" />
</.accordion_indicator>
</:indicator>
</.accordion_trigger>
<.accordion_content item={item}>
<p>Consectetur adipiscing elit. Sed sodales ullamcorper tristique.</p>
</.accordion_content>
</.accordion_item>
<.accordion_item :let={item} ctx={ctx} value="duis">
<.accordion_trigger item={item}>
Duis dictum gravida odio ac pharetra?
<:indicator>
<.accordion_indicator item={item}>
<.heroicon name="hero-chevron-right" />
</.accordion_indicator>
</:indicator>
</.accordion_trigger>
<.accordion_content item={item}>
<p>Nullam eget vestibulum ligula, at interdum tellus.</p>
</.accordion_content>
</.accordion_item>
<.accordion_item :let={item} ctx={ctx} value="donec">
<.accordion_trigger item={item}>
Donec condimentum ex mi
<:indicator>
<.accordion_indicator item={item}>
<.heroicon name="hero-chevron-right" />
</.accordion_indicator>
</:indicator>
</.accordion_trigger>
<.accordion_content item={item}>
<p>Congue molestie ipsum gravida a. Sed ac eros luctus.</p>
</.accordion_content>
</.accordion_item>
</.accordion_root>
</.accordion>From a list
<.accordion :let={ctx} compound id="faq" class="accordion">
<.accordion_root ctx={ctx}>
<.accordion_item :for={entry <- @items} :let={item} ctx={ctx} value={entry.value}>
<.accordion_trigger item={item}>
{entry.label}
<:indicator>
<.accordion_indicator item={item}>
<.heroicon name="hero-chevron-right" />
</.accordion_indicator>
</:indicator>
</.accordion_trigger>
<.accordion_content item={item}>
<p>{entry.content}</p>
</.accordion_content>
</.accordion_item>
</.accordion_root>
</.accordion>API
Requires a stable id on <.accordion>.
| Function | Action | Returns |
|---|---|---|
set_value/2 | Set open items (client) | %Phoenix.LiveView.JS{} |
set_value/3 | Set open items (server) | socket |
value/2 | Read open items (client) | %Phoenix.LiveView.JS{} |
value/3 | Read open items (server) | socket |
focused/2 | Read focused item (client) | %Phoenix.LiveView.JS{} |
focused/3 | Read focused item (server) | socket |
item_state/3 | Read one item state (client) | %Phoenix.LiveView.JS{} |
item_state/4 | Read one item state (server) | socket |
Events
Pick an event name and pass it to on_* on <.accordion>.
Server events
| Event | When | Payload |
|---|---|---|
on_value_change="items_changed" | Open items change | %{"id" => id, "value" => values} — list of open item value strings |
on_focus_change="focus_changed" | Focused item changes | %{"id" => id, "value" => value} — item value or nil |
on_value_change
<.accordion
id="faq"
class="accordion"
on_value_change="items_changed"
items={
Corex.Content.new([
%{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
%{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
%{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
])
}
>
<:indicator><.heroicon name="hero-chevron-right" /></:indicator>
</.accordion>def handle_event("items_changed", %{"id" => _id, "value" => values}, socket) do
{:noreply, assign(socket, :open_items, values)}
endon_focus_change
<.accordion
id="faq"
class="accordion"
on_focus_change="focus_changed"
items={
Corex.Content.new([
%{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
%{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
%{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
])
}
>
<:indicator><.heroicon name="hero-chevron-right" /></:indicator>
</.accordion>def handle_event("focus_changed", %{"id" => _id, "value" => item}, socket) do
{:noreply, assign(socket, :focused_item, item)}
endClient events
| Event | When | event.detail |
|---|---|---|
on_value_change_client="items-changed" | Open items change | id, value, previousValue, added, removed |
on_focus_change_client="focus-changed" | Focused item changes | id, value |
on_value_change_client
<.accordion
id="faq"
class="accordion"
on_value_change_client="items-changed"
items={
Corex.Content.new([
%{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
%{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
%{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
])
}
>
<:indicator><.heroicon name="hero-chevron-right" /></:indicator>
</.accordion>document.getElementById("faq")?.addEventListener("items-changed", (e) => {
console.log(e.detail.value, e.detail.added, e.detail.removed);
});on_focus_change_client
<.accordion
id="faq"
class="accordion"
on_focus_change_client="focus-changed"
items={
Corex.Content.new([
%{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
%{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
%{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
])
}
>
<:indicator><.heroicon name="hero-chevron-right" /></:indicator>
</.accordion>document.getElementById("faq")?.addEventListener("focus-changed", (e) => {
console.log(e.detail.value);
});Patterns
Async
If items are not ready in mount/3—for example they load from the database or an external service—use assign_async/3, render inside <.async_result>, and put <.accordion_skeleton> in the :loading slot while the async assign is still pending.
defmodule MyAppWeb.AccordionAsyncLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
socket =
socket
|> assign_async(:accordion, fn ->
items =
Corex.Content.new([
%{
value: "lorem",
label: "Lorem ipsum dolor sit amet",
content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique.",
disabled: true
},
%{
value: "duis",
label: "Duis dictum gravida odio ac pharetra?",
content: "Nullam eget vestibulum ligula, at interdum tellus."
},
%{
value: "donec",
label: "Donec condimentum ex mi",
content: "Congue molestie ipsum gravida a. Sed ac eros luctus."
}
])
{:ok, %{accordion: %{items: items, value: ["duis", "donec"]}}}
end)
{:ok, socket}
end
def render(assigns) do
~H"""
<.async_result :let={accordion} assign={@accordion}>
<:loading>
<.accordion_skeleton count={3} class="accordion" />
</:loading>
<:failed>Could not load accordion.</:failed>
<.accordion
id="async-accordion"
class="accordion"
items={accordion.items}
value={accordion.value}
/>
</.async_result>
"""
end
endControlled
For server-owned open state—validation, forms, or rules that must run before items open—set controlled, bind value, and handle on_value_change in LiveView so assigns stay the source of truth.
defmodule MyAppWeb.AccordionLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, :accordion_value, ["lorem"])}
end
def handle_event("accordion_value_changed", %{"id" => _id, "value" => value}, socket) do
{:noreply, assign(socket, :accordion_value, value)}
end
def render(assigns) do
~H"""
<.accordion
id="my-accordion"
controlled
value={@accordion_value}
on_value_change="accordion_value_changed"
class="accordion"
items={
Corex.Content.new([
%{
value: "lorem",
label: "Lorem ipsum dolor sit amet",
content: "Consectetur adipiscing elit."
},
%{
value: "duis",
label: "Duis dictum gravida odio ac pharetra?",
content: "Nullam eget vestibulum ligula."
}
])
}
/>
"""
end
endStream
Use Phoenix.LiveView.stream/3 to add or remove accordion items at runtime. Keep a list assign in sync with the stream and pass it as items. Configure dom_id to match each item element id (accordion:my-accordion:item:#{value}).
defmodule MyAppWeb.AccordionStreamLive do
use MyAppWeb, :live_view
@initial_items [
%{value: "1", label: "Lorem ipsum", content: "Consectetur adipiscing elit."},
%{value: "2", label: "Duis dictum", content: "Nullam eget vestibulum ligula."},
%{value: "3", label: "Donec condimentum", content: "Congue molestie ipsum gravida a."}
]
def mount(_params, _session, socket) do
{:ok,
socket
|> stream_configure(:items, dom_id: &"accordion:my-accordion:item:#{&1.value}")
|> stream(:items, @initial_items)
|> assign(:items_list, @initial_items)
|> assign(:next_id, 4)}
end
def handle_event("add_item", _params, socket) do
id = to_string(socket.assigns.next_id)
item = %{value: id, label: "Item #{id}", content: "Content for item #{id}."}
{:noreply,
socket
|> stream_insert(:items, item)
|> assign(:items_list, socket.assigns.items_list ++ [item])
|> assign(:next_id, socket.assigns.next_id + 1)}
end
def render(assigns) do
~H"""
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new(@items_list)} />
"""
end
endAnimation
JS
Built-in height and opacity (Web Animations API). Set animation_options with Corex.Animation.Height for duration, easing, and opacity.
<.accordion
class="accordion"
animation="js"
animation_options={%Corex.Animation.Height{duration: 0.3, easing: "ease-out", opacity_start: 0, opacity_end: 1}}
items={
Corex.Content.new([
%{
label: "Lorem ipsum dolor sit amet",
content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
},
%{
label: "Duis dictum gravida odio ac pharetra?",
content: "Nullam eget vestibulum ligula, at interdum tellus."
},
%{
label: "Donec condimentum ex mi",
content: "Congue molestie ipsum gravida a. Sed ac eros luctus."
}
])
}
>
<:indicator>
<.heroicon name="hero-chevron-right" />
</:indicator>
</.accordion>Instant
Items open and close immediately. Content visibility uses the native hidden attribute; there is no height animation.
<.accordion
class="accordion"
animation="instant"
items={
Corex.Content.new([
%{
label: "Lorem ipsum dolor sit amet",
content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
},
%{
label: "Duis dictum gravida odio ac pharetra?",
content: "Nullam eget vestibulum ligula, at interdum tellus."
},
%{
label: "Donec condimentum ex mi",
content: "Congue molestie ipsum gravida a. Sed ac eros luctus."
}
])
}
>
<:indicator>
<.heroicon name="hero-chevron-right" />
</:indicator>
</.accordion>Custom (Motion)
Set animation="custom" and on_value_change_client to run Motion (or any JS) on open and close. Content stays in the DOM (hidden is not toggled). Each change fires a CustomEvent on the accordion with:
// event.detail — AccordionChangedDetail
{ id, value, previousValue, added, removed }added and removed list which item values opened or closed so you can animate only those items. Register the listener after mount (and again after LiveView navigation if the DOM is replaced).
<.accordion
class="accordion"
animation="custom"
on_value_change_client="my-accordion-changed"
items={
Corex.Content.new([
%{
label: "Lorem ipsum dolor sit amet",
content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
},
%{
label: "Duis dictum gravida odio ac pharetra?",
content: "Nullam eget vestibulum ligula, at interdum tellus."
},
%{
label: "Donec condimentum ex mi",
content: "Congue molestie ipsum gravida a. Sed ac eros luctus."
}
])
}
>
<:indicator>
<.heroicon name="hero-chevron-right" />
</:indicator>
</.accordion>import { animate } from "motion"
import {
findAccordionContent,
animateHeightOpen,
animateHeightClose,
} from "corex"
const reducedMotion = () =>
window.matchMedia("(prefers-reduced-motion: reduce)").matches
document.addEventListener("my-accordion-changed", (e) => {
const root = document.getElementById(e.detail.id)
if (!root) return
e.detail.added.forEach((v) => {
const el = findAccordionContent(root, v)
if (!el) return
animateHeightOpen(el, { animator: animate, duration: 0.55, easing: [0.16, 1, 0.3, 1] })
if (!reducedMotion()) {
animate(
el,
{ filter: ["blur(12px)", "blur(0px)"], scale: [0.96, 1] },
{ duration: 0.6, easing: [0.16, 1, 0.3, 1] },
)
}
})
e.detail.removed.forEach((v) => {
const el = findAccordionContent(root, v)
if (!el) return
animateHeightClose(el, { animator: animate, duration: 0.32, easing: [0.7, 0, 0.84, 0] })
if (!reducedMotion()) {
animate(
el,
{ filter: ["blur(0px)", "blur(10px)"], scale: [1, 0.97] },
{ duration: 0.3, easing: "ease-in" },
)
}
})
})Style
Target parts with data-scope and data-part, or use Corex Design: import tokens and accordion.css, then set class="accordion" on <.accordion>.
[data-scope="accordion"][data-part="root"] {}
[data-scope="accordion"][data-part="item"] {}
[data-scope="accordion"][data-part="item-trigger"] {}
[data-scope="accordion"][data-part="item-text"] {}
[data-scope="accordion"][data-part="item-content"] {}
[data-scope="accordion"][data-part="item-indicator"] {}@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/accordion.css";Stack modifiers on the host (class on <.accordion>). Combine axes, for example accordion accordion--accent accordion--lg.
Color
Semantic palette on the open item trigger (from design tokens).
| Modifier | Classes |
|---|---|
| Default | accordion |
| Accent | accordion accordion--accent |
| Brand | accordion accordion--brand |
| Alert | accordion accordion--alert |
| Info | accordion accordion--info |
| Success | accordion accordion--success |
Size
Trigger padding, gap, min-height, and content spacing.
| Modifier | Classes |
|---|---|
| Default | accordion |
| SM | accordion accordion--sm |
| MD | accordion accordion--md |
| LG | accordion accordion--lg |
| XL | accordion accordion--xl |
Text
Font size on trigger and content.
| Modifier | Classes |
|---|---|
| Default | accordion |
| SM | accordion accordion--text-sm |
| XL | accordion accordion--text-xl |
| 2XL | accordion accordion--text-2xl |
| 4XL | accordion accordion--text-4xl |
Rounded
Corner radius on trigger and content.
| Modifier | Classes |
|---|---|
| Default | accordion |
| None | accordion accordion--rounded-none |
| SM | accordion accordion--rounded-sm |
| MD | accordion accordion--rounded-md |
| LG | accordion accordion--rounded-lg |
| XL | accordion accordion--rounded-xl |
| Full | accordion accordion--rounded-full |
Max width
| Modifier | Classes |
|---|---|
| Default | accordion |
| None | accordion max-w-none |
| 5XS | accordion max-w-5xs |
| 2XS | accordion max-w-2xs |
| XS | accordion max-w-xs |
| SM | accordion max-w-sm |
| MD | accordion max-w-md |
| LG | accordion max-w-lg |
| XL | accordion max-w-xl |
| 2XL | accordion max-w-2xl |
| 5XL | accordion max-w-5xl |
Summary
Components
Renders an accordion. See the module documentation for list-driven items, With slots, Custom slots, Manual and Compound modes, patterns, API, and events.
Renders a loading skeleton for the accordion component.
Compounds
Renders the content area for an accordion item.
Renders the indicator for an accordion item.
Renders an accordion item. Use inside accordion compound mode with :let={ctx}.
Renders the root container for an accordion in compound mode.
Renders the trigger button for an accordion item.
API
Read the focused item from phx-click. Dispatches corex:accordion:focused. Optional respond_to: :server (default), :client, or :both.
Read the focused item from handle_event (accordion_focused). Same replies as focused/2.
Read expanded, focused, and disabled state for one item from phx-click. Dispatches corex:accordion:item-state. Optional disabled: and respond_to: :server (default), :client, or :both.
Read item state from handle_event (accordion_item_state). Same replies as item_state/3.
Open or close items from phx-click. Pass a list (["lorem"]), a comma string ("lorem,donec"), or [] to close all.
Open or close items from handle_event. Pushes accordion_set_value (no reply event).
Read open items from phx-click. Dispatches corex:accordion:value. Optional respond_to: :server (default), :client, or :both.
Read open items from handle_event (accordion_value). Same replies as value/2.
Components
Renders an accordion. See the module documentation for list-driven items, With slots, Custom slots, Manual and Compound modes, patterns, API, and events.
Attributes
id(:string) - DOM id on the accordion root. Used byset_value,value,focused, anditem_state; auto-generated when omitted.items(:list) - List of%Corex.Content.Item{}fromCorex.Content.new/1. Defaults to[].value(:any) - Initial or controlled open state: one string or a list of strings (valueof each item). Defaults to[].compound(:boolean) - Enable compound mode. Use with :let={ctx} and sub-components to fully control structure. Defaults tofalse.controlled(:boolean) - When true, LiveView owns open items viavalueandon_value_change. Defaults tofalse.collapsible(:boolean) - Whether the accordion is collapsible. Defaults totrue.multiple(:boolean) - Whether the accordion allows multiple items to be selected. Defaults totrue.animation(:string) - How items animate when opening or closing.instant— togglehiddenimmediatelyjs— built-in height and opacity (animation_options/Corex.Animation.Height)custom— no built-in animation; useon_value_change_clientwith Motion or other JS
Defaults to
"js". Must be one of"instant","js", or"custom".animation_options(Corex.Animation.Height) - Used whenanimationisjs. Ignored forinstantandcustom. SeeCorex.Animation.Height. Defaults to%Corex.Animation.Height{duration: 0.3, easing: "ease", opacity_start: 0.0, opacity_end: 1.0, block_interaction: false}.orientation(:string) - The orientation of the accordion. Defaults to"vertical". Must be one of"horizontal", or"vertical".dir(:string) - The direction of the accordion. When nil, derived from document (html lang + config :rtl_locales). Defaults tonil. Must be one ofnil,"ltr", or"rtl".on_value_change(:string) - LiveView event when open items change. Pick any event name.<.accordion id="faq" class="accordion" on_value_change="items_changed" items={ Corex.Content.new([ %{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."}, %{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."}, %{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."} ]) } > <:indicator><.heroicon name="hero-chevron-right" /></:indicator> </.accordion>def handle_event("items_changed", %{"id" => _id, "value" => values}, socket) do {:noreply, assign(socket, :open_items, values)} endDefaults to
nil.on_value_change_client(:string) - Browser event on the accordion element when open items change (same moment ason_value_change).<.accordion id="faq" class="accordion" on_value_change_client="items-changed" items={ Corex.Content.new([ %{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."}, %{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."}, %{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."} ]) } > <:indicator><.heroicon name="hero-chevron-right" /></:indicator> </.accordion>document.getElementById("faq")?.addEventListener("items-changed", (e) => { console.log(e.detail.value, e.detail.added, e.detail.removed); });Defaults to
nil.on_focus_change(:string) - LiveView event when keyboard focus moves to another item.<.accordion id="faq" class="accordion" on_focus_change="focus_changed" items={ Corex.Content.new([ %{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."}, %{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."}, %{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."} ]) } > <:indicator><.heroicon name="hero-chevron-right" /></:indicator> </.accordion>def handle_event("focus_changed", %{"id" => _id, "value" => item}, socket) do {:noreply, assign(socket, :focused_item, item)} endDefaults to
nil.on_focus_change_client(:string) - Browser event on the accordion element when focus moves.<.accordion id="faq" class="accordion" on_focus_change_client="focus-changed" items={ Corex.Content.new([ %{label: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."}, %{label: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."}, %{label: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."} ]) } > <:indicator><.heroicon name="hero-chevron-right" /></:indicator> </.accordion>document.getElementById("faq")?.addEventListener("focus-changed", (e) => { console.log(e.detail.value); });Defaults to
nil.Global attributes are accepted.
Slots
inner_block- Compound mode inner content. Use with thecompoundattribute and:let={ctx}.ctxis a map with keys:id,values,orientation,dir.indicator- Optional slot after each trigger. With:items, use:let={item}. Without:items(manual mode), use one slot per item and a matchingvalueon:triggerand:content. Accepts attributes:value(:string)class(:string)
trigger- With:items, optional custom trigger; use:let={item}. Without:items(manual mode), one slot per item withvalue(or defaultitem-0, …). Accepts attributes:value(:string)class(:string)disabled(:boolean)
content- With:items, optional custom content; use:let={item}. Without:items(manual mode), one slot per item withvalue(or defaultitem-0, …). Accepts attributes:value(:string)class(:string)disabled(:boolean)
Renders a loading skeleton for the accordion component.
Attributes
count(:integer) - Defaults to3.- Global attributes are accepted.
Slots
trigger- Accepts attributes:class(:string)
indicator- Accepts attributes:class(:string)
content- Accepts attributes:class(:string)
Compounds
Renders the content area for an accordion item.
Use inside accordion_item with :let={item}, passing the yielded item as the item attr.
Attributes
item(:map) (required) - The item struct yielded by accordion_item via :let={item}.animation(:string) - Override animation mode; defaults to the parent accordionanimationfrom compound ctx. Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
Renders the indicator for an accordion item.
Use inside accordion_trigger inner block, passing the same item from accordion_item.
Attributes
item(:map) (required) - The item struct yielded by accordion_item via :let={item}.- Global attributes are accepted.
Slots
inner_block(required)
Renders an accordion item. Use inside accordion compound mode with :let={ctx}.
Yields the %Item{} struct via :let for use in child parts.
Attributes
ctx(:map) (required) - The context map yielded by the parent accordion via :let={ctx}.value(:string) (required) - The unique value identifying this item.disabled(:boolean) - Whether the item is disabled. Defaults tofalse.label(:string) - Visible item label for unique region names. Defaults tonil.- Global attributes are accepted.
Slots
inner_block
Renders the root container for an accordion in compound mode.
Use inside accordion compound mode with :let={ctx}, wrapping all accordion_item components.
Attributes
ctx(:map) (required) - The context map yielded by the parent accordion via :let={ctx}.- Global attributes are accepted.
Slots
inner_block(required)
Renders the trigger button for an accordion item.
Use inside accordion_item with :let={item}, passing the yielded item as the item attr.
Place accordion_indicator inside this component's inner block if needed.
Attributes
item(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)indicator
API
Read the focused item from phx-click. Dispatches corex:accordion:focused. Optional respond_to: :server (default), :client, or :both.
| Reply | Payload | |
|---|---|---|
| Server | accordion_focused_response | %{"id" => id, "value" => value} |
| Client | accordion-focused on the accordion | detail: id, value |
<.action phx-click={Corex.Accordion.focused("my-accordion", respond_to: :both)}>Focused item</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>document.getElementById("my-accordion")?.dispatchEvent(
new CustomEvent("corex:accordion:focused", {
bubbles: false,
detail: { respond_to: "both" }
})
);def handle_event("accordion_focused_response", %{"id" => _id, "value" => item}, socket) do
{:noreply, assign(socket, :focused_item, item)}
end
Read the focused item from handle_event (accordion_focused). Same replies as focused/2.
| Reply | Payload |
|---|---|
accordion_focused_response | %{"id" => id, "value" => value} |
<.action phx-click="read_focus">Focused item</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>def handle_event("read_focus", _params, socket) do
{:noreply, Corex.Accordion.focused(socket, "my-accordion", respond_to: :server)}
end
def handle_event("accordion_focused_response", %{"id" => _id, "value" => item}, socket) do
{:noreply, assign(socket, :focused_item, item)}
end
Read expanded, focused, and disabled state for one item from phx-click. Dispatches corex:accordion:item-state. Optional disabled: and respond_to: :server (default), :client, or :both.
| Reply | Payload | |
|---|---|---|
| Server | accordion_item_state_response | %{"id" => id, "value" => value, "state" => %{"expanded" => bool, "focused" => bool, "disabled" => bool}} |
| Client | accordion-item-state on the accordion | detail: id, value, state |
<.action phx-click={Corex.Accordion.item_state("my-accordion", "lorem", respond_to: :both)}>State for Lorem</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>document.getElementById("my-accordion")?.dispatchEvent(
new CustomEvent("corex:accordion:item-state", {
bubbles: false,
detail: { value: "lorem", respond_to: "both" }
})
);def handle_event("accordion_item_state_response", %{"id" => _id, "value" => item, "state" => state}, socket) do
{:noreply, assign(socket, :item_state, {item, state})}
end
Read item state from handle_event (accordion_item_state). Same replies as item_state/3.
| Reply | Payload |
|---|---|
accordion_item_state_response | %{"id" => id, "value" => value, "state" => %{"expanded" => bool, "focused" => bool, "disabled" => bool}} |
<.action phx-click="read_lorem">State for Lorem</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>def handle_event("read_lorem", _params, socket) do
{:noreply, Corex.Accordion.item_state(socket, "my-accordion", "lorem", respond_to: :server)}
end
def handle_event("accordion_item_state_response", %{"id" => _id, "value" => item, "state" => state}, socket) do
{:noreply, assign(socket, :item_state, {item, state})}
end
Open or close items from phx-click. Pass a list (["lorem"]), a comma string ("lorem,donec"), or [] to close all.
<.action phx-click={Corex.Accordion.set_value("my-accordion", "lorem")}>Open Lorem</.action>
<.action phx-click={Corex.Accordion.set_value("my-accordion", [])}>Close all</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}>
<:indicator><.heroicon name="hero-chevron-right" /></:indicator>
</.accordion>document.getElementById("my-accordion")?.dispatchEvent(
new CustomEvent("corex:accordion:set-value", {
bubbles: false,
detail: { value: ["lorem"] }
})
);
Open or close items from handle_event. Pushes accordion_set_value (no reply event).
<.action phx-click="open_lorem" phx-value-value="lorem">Open Lorem</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>def handle_event("open_lorem", %{"value" => value}, socket) do
{:noreply, Corex.Accordion.set_value(socket, "my-accordion", value)}
end
Read open items from phx-click. Dispatches corex:accordion:value. Optional respond_to: :server (default), :client, or :both.
| Reply | Payload | |
|---|---|---|
| Server | accordion_value_response | %{"id" => id, "value" => values} |
| Client | accordion-value on the accordion | detail: id, value |
<.action phx-click={Corex.Accordion.value("my-accordion", respond_to: :both)}>Which items are open?</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>document.getElementById("my-accordion")?.dispatchEvent(
new CustomEvent("corex:accordion:value", {
bubbles: false,
detail: { respond_to: "both" }
})
);def handle_event("accordion_value_response", %{"id" => _id, "value" => values}, socket) do
{:noreply, assign(socket, :open_items, values)}
endvalues is a list of open item value strings, or nil.
Read open items from handle_event (accordion_value). Same replies as value/2.
| Reply | Payload |
|---|---|
accordion_value_response | %{"id" => id, "value" => values} |
<.action phx-click="read_items">Which items are open?</.action>
<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
%{value: "lorem", label: "Lorem", content: "Lorem body."},
%{value: "duis", label: "Duis", content: "Duis body."},
%{value: "donec", label: "Donec", content: "Donec body."}
])}><:indicator><.heroicon name="hero-chevron-right" /></:indicator></.accordion>def handle_event("read_items", _params, socket) do
{:noreply, Corex.Accordion.value(socket, "my-accordion", respond_to: :server)}
end
def handle_event("accordion_value_response", %{"id" => _id, "value" => values}, socket) do
{:noreply, assign(socket, :open_items, values)}
end