Corex.Accordion (Corex v0.1.0-beta.2)

View Source

Phoenix implementation of the Zag.js Accordion.

Anatomy

Minimal

<.accordion
class="accordion"
items={
  Corex.Content.new([
    %{trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
    %{trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
    %{trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a."}
  ])
}
/>

With slots

With items and <:indicator> slot so every panel shares the same indicator markup.

<.accordion
class="accordion"
items={
  Corex.Content.new([
    %{trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit."},
    %{trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula."},
    %{trigger: "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",
        trigger: "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"}
      },
      %{
        trigger: "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",
        trigger: "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.trigger}
  </: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.trigger}
        <: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>

Patterns

Async

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",
              trigger: "Lorem ipsum dolor sit amet",
              content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique.",
              disabled: true
            },
            %{
              value: "duis",
              trigger: "Duis dictum gravida odio ac pharetra?",
              content: "Nullam eget vestibulum ligula, at interdum tellus."
            },
            %{
              value: "donec",
              trigger: "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
end

Controlled

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",
            trigger: "Lorem ipsum dolor sit amet",
            content: "Consectetur adipiscing elit."
          },
          %{
            value: "duis",
            trigger: "Duis dictum gravida odio ac pharetra?",
            content: "Nullam eget vestibulum ligula."
          }
        ])
      }
    />
    """
  end
end

Animation

js

Built-in height and opacity via the Web Animations API. Tune timing with animation_options using Corex.Animation.Height.

<.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([
      %{
        trigger: "Lorem ipsum dolor sit amet",
        content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
      },
      %{
        trigger: "Duis dictum gravida odio ac pharetra?",
        content: "Nullam eget vestibulum ligula, at interdum tellus."
      },
      %{
        trigger: "Donec condimentum ex mi",
        content: "Congue molestie ipsum gravida a. Sed ac eros luctus."
      }
    ])
  }
>
  <:indicator>
    <.heroicon name="hero-chevron-right" />
  </:indicator>
</.accordion>

instant

Zag toggles the native hidden attribute; no height animation.

<.accordion
  class="accordion"
  animation="instant"
  items={
    Corex.Content.new([
      %{
        trigger: "Lorem ipsum dolor sit amet",
        content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
      },
      %{
        trigger: "Duis dictum gravida odio ac pharetra?",
        content: "Nullam eget vestibulum ligula, at interdum tellus."
      },
      %{
        trigger: "Donec condimentum ex mi",
        content: "Congue molestie ipsum gravida a. Sed ac eros luctus."
      }
    ])
  }
>
  <:indicator>
    <.heroicon name="hero-chevron-right" />
  </:indicator>
</.accordion>

custom

The hook removes hidden and dispatches a browser CustomEvent when the value changes. Use on_value_change_client for the event name. The event detail is enriched with deltas so user code rarely needs DOM lookups beyond the affected items:

// event.detail (AccordionChangedDetail)
{ id, value, previousValue, added, removed }

Animate panels in your own JS. The example below also seeds initial closed-state styling on mount and after LiveView navigations.

<.accordion
  id="accordion-custom-animate"
  class="accordion"
  animation="custom"
  on_value_change_client="my-accordion-changed"
  items={
    Corex.Content.new([
      %{
        trigger: "Lorem ipsum dolor sit amet",
        content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
      },
      %{
        trigger: "Duis dictum gravida odio ac pharetra?",
        content: "Nullam eget vestibulum ligula, at interdum tellus."
      },
      %{
        trigger: "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" },
      )
    }
  })
})

API

These helpers target one accordion via its DOM id. Use them from LiveView (server), from HEEx bindings (client binding), or from plain JavaScript/TypeScript (client JS).

For value, focused, and item_state, you can pass respond_to: :server | :client | :both to control whether the response is pushed to LiveView, dispatched as a DOM event, or both.

Set value

Open/close panels programmatically.

Server

<.action phx-click="open_lorem" class="button button--sm">Open Lorem</.action>
<.action phx-click="open_lorem_and_donec" class="button button--sm">Lorem and Donec</.action>
<.action phx-click="close_all" class="button button--sm">Close all</.action>

<.accordion
  id="my-accordion"
  class="accordion"
  items={
    Corex.Content.new([
      %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
      %{value: "duis", trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula, at interdum tellus."},
      %{value: "donec", trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a. Sed ac eros luctus."}
    ])
  }
/>
def handle_event("open_lorem", _params, socket) do
  {:noreply, Corex.Accordion.set_value(socket, "my-accordion", ["lorem"])}
end

def handle_event("open_lorem_and_donec", _params, socket) do
  {:noreply, Corex.Accordion.set_value(socket, "my-accordion", ["lorem", "donec"])}
end

def handle_event("close_all", _params, socket) do
  {:noreply, Corex.Accordion.set_value(socket, "my-accordion", [])}
end

Client Binding

<.action phx-click={Corex.Accordion.set_value("my-accordion", ["lorem"])} class="button button--sm">
  Open Lorem
</.action>
<.action
  phx-click={Corex.Accordion.set_value("my-accordion", ["lorem", "donec"])}
  class="button button--sm"
>
  Lorem and Donec
</.action>
<.action phx-click={Corex.Accordion.set_value("my-accordion", [])} class="button button--sm">
  Close all
</.action>

<.accordion
  id="my-accordion"
  class="accordion"
  items={
    Corex.Content.new([
      %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
      %{value: "duis", trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula, at interdum tellus."},
      %{value: "donec", trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a. Sed ac eros luctus."}
    ])
  }
/>

Client JS

const el = document.getElementById("my-accordion");
const set = (value) =>
  el?.dispatchEvent(
    new CustomEvent("corex:accordion:set-value", {
      bubbles: false,
      detail: { value },
    })
  );

set(["lorem"]);
set(["lorem", "donec"]);
set([]);
const el = document.getElementById("my-accordion");
const set = (value: string[]) =>
  el?.dispatchEvent(
    new CustomEvent("corex:accordion:set-value", {
      bubbles: false,
      detail: { value },
    })
  );

set(["lorem"]);
set(["lorem", "donec"]);
set([]);

Read value

Ask the browser what is currently open.

Server

def handle_event("read_value", _params, socket) do
  {:noreply, Corex.Accordion.value(socket, "my-accordion")}
end

def handle_event("accordion_value_response", %{"id" => "my-accordion", "value" => value}, socket) do
  {:noreply, assign(socket, :accordion_value, value)}
end

Client Binding

<.action phx-click={Corex.Accordion.value("my-accordion")} class="button button--sm">
  Value
</.action>

Client JS

const el = document.getElementById("my-accordion");
el?.dispatchEvent(new CustomEvent("corex:accordion:value", { bubbles: false, detail: {} }));

el?.addEventListener("accordion-value", (e) => {
  console.log(e.detail.id, e.detail.value);
});
const el = document.getElementById("my-accordion");
type Detail = { id: string; value: string[] | null };

el?.dispatchEvent(new CustomEvent("corex:accordion:value", { bubbles: false, detail: {} }));
el?.addEventListener("accordion-value", (event: Event) => {
  const detail = (event as CustomEvent<Detail>).detail;
  console.log(detail.id, detail.value);
});

Read focused

Ask the browser which item is currently focused.

Server

def handle_event("read_focused", _params, socket) do
  {:noreply, Corex.Accordion.focused(socket, "my-accordion")}
end

def handle_event("accordion_focused_response", %{"id" => "my-accordion", "value" => value}, socket) do
  {:noreply, assign(socket, :accordion_focused, value)}
end

Client Binding

<.action phx-click={Corex.Accordion.focused("my-accordion")} class="button button--sm">
  Focused
</.action>

Client JS

const el = document.getElementById("my-accordion");
el?.dispatchEvent(new CustomEvent("corex:accordion:focused", { bubbles: false, detail: {} }));

el?.addEventListener("accordion-focused", (e) => {
  console.log(e.detail.id, e.detail.value);
});
const el = document.getElementById("my-accordion");
type Detail = { id: string; value: string | null };

el?.dispatchEvent(new CustomEvent("corex:accordion:focused", { bubbles: false, detail: {} }));
el?.addEventListener("accordion-focused", (event: Event) => {
  const detail = (event as CustomEvent<Detail>).detail;
  console.log(detail.id, detail.value);
});

Read item state

Ask the browser for one item's state.

Server

def handle_event("read_item_state", _params, socket) do
  {:noreply, Corex.Accordion.item_state(socket, "my-accordion", "lorem", disabled: false)}
end

def handle_event(
      "accordion_item_state_response",
      %{"id" => "my-accordion", "value" => value, "state" => state},
      socket
    ) do
  {:noreply, assign(socket, :accordion_item_state, {value, state})}
end

Client Binding

<.action
  phx-click={Corex.Accordion.item_state("my-accordion", "lorem", disabled: false)}
  class="button button--sm"
>
  item_state("lorem")
</.action>
<.action
  phx-click={Corex.Accordion.item_state("my-accordion", "donec", disabled: true)}
  class="button button--sm"
>
  item_state("donec", disabled)
</.action>

Client JS

const el = document.getElementById("my-accordion");
el?.addEventListener("accordion-item-state", (e) => console.log(e.detail));

el?.dispatchEvent(
  new CustomEvent("corex:accordion:item-state", {
    bubbles: false,
    detail: { value: "lorem", disabled: false, respond_to: "client" },
  })
);
const el = document.getElementById("my-accordion");
type State = { expanded: boolean; focused: boolean; disabled: boolean };
type Detail = { id: string; value: string; state: State };

el?.addEventListener("accordion-item-state", (event: Event) => {
  const detail = (event as CustomEvent<Detail>).detail;
  console.log(detail.id, detail.value, detail.state);
});

Events

Use on_* to receive value/focus changes either in LiveView (pushEvent) or in the browser (CustomEvent).

Server (LiveView)

<.accordion
  id="my-accordion"
  class="accordion"
  items={
    Corex.Content.new([
      %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
      %{value: "duis", trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula, at interdum tellus."},
      %{value: "donec", trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a. Sed ac eros luctus."}
    ])
  }
  on_value_change="accordion_value_changed"
  on_focus_change="accordion_focus_changed"
/>
def handle_event("accordion_value_changed", %{"id" => id, "value" => value}, socket) do
  {:noreply, assign(socket, :accordion_open, {id, value})}
end

def handle_event("accordion_focus_changed", %{"id" => id, "value" => value}, socket) do
  {:noreply, assign(socket, :accordion_focused, {id, value})}
end

Client (CustomEvent / DOM)

<.accordion
  id="my-accordion"
  class="accordion"
  items={
    Corex.Content.new([
      %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
      %{value: "duis", trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula, at interdum tellus."},
      %{value: "donec", trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a. Sed ac eros luctus."}
    ])
  }
  on_value_change_client="accordion-value-changed"
  on_focus_change_client="accordion-focus-changed"
/>
const el = document.getElementById("my-accordion");

el?.addEventListener("accordion-value-changed", (event) => {
  const d = event.detail;
  console.log(d.id, d.value, "added:", d.added, "removed:", d.removed);
});

el?.addEventListener("accordion-focus-changed", (event) => {
  const d = event.detail;
  console.log(d.id, d.value);
});
import type { AccordionChangedDetail } from "corex";

const el = document.getElementById("my-accordion");
type FocusDetail = { id: string; value: string | null };

el?.addEventListener("accordion-value-changed", (event: Event) => {
  const d = (event as CustomEvent<AccordionChangedDetail>).detail;
  console.log(d.id, d.value, "added:", d.added, "removed:", d.removed);
});

el?.addEventListener("accordion-focus-changed", (event: Event) => {
  const d = (event as CustomEvent<FocusDetail>).detail;
  console.log(d.id, d.value);
});

Style

Zag exposes data-scope and data-part on each element:

[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-content"] {}
[data-scope="accordion"][data-part="item-indicator"] {}

With Corex Design, import tokens and the accordion stylesheet, then add the accordion class and modifiers:

@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/accordion.css";
<.accordion
  class="accordion accordion--accent accordion--lg"
  items={
    Corex.Content.new([
      %{trigger: "First", content: "First body."},
      %{trigger: "Second", content: "Second body."}
    ])
  }
/>

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 panel 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

Requests the accordion's focused item value from the browser. Returns a Phoenix.LiveView.JS command.

Requests the accordion's focused item value from the client. Pushes a LiveView event handled by the hook.

Zag getItemState for one item. Pass value and keyword opts (:disabled, :respond_to).

Sets the accordion value from client-side. Returns a Phoenix.LiveView.JS command.

Sets the accordion value from server-side. Pushes a LiveView event.

Requests the accordion's current open values from the browser. Returns a Phoenix.LiveView.JS command.

Requests the accordion's current open values from the client. Pushes a LiveView event handled by the hook.

Components

accordion(assigns)

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) - The id of the accordion, useful for API to identify the accordion.

  • items (:list) - The items of the accordion, must be a list of %Corex.Content.Item{} structs. Defaults to [].

  • value (:any) - The initial value or controlled value. Accepts a string or list of strings. Defaults to [].

  • compound (:boolean) - Enable compound mode. Use with :let={ctx} and sub-components to fully control structure. Defaults to false.

  • controlled (:boolean) - Whether the accordion is controlled. Only in LiveView, the on_value_change event is required. Defaults to false.

  • collapsible (:boolean) - Whether the accordion is collapsible. Defaults to true.

  • multiple (:boolean) - Whether the accordion allows multiple items to be selected. Defaults to true.

  • animation (:string) - Animation mode for content open/close.

    • instant — no animation, content opens/closes instantly via native hidden attribute
    • js — built-in animation via Web Animations API (opacity + height); tune with animation_options (Corex.Animation.Height)
    • custom — removes hidden, full JS control via on_value_change_client

    Defaults to "js". Must be one of "instant", "js", or "custom".

  • animation_options (Corex.Animation.Height) - Wired to the host when animation is js only. Custom transitions ignore this assign. See Corex.Animation.Height (opacity, height, block_interaction). 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 to "ltr". Must be one of "ltr", or "rtl".

  • on_value_change (:string) - LiveView event name for pushEvent when the open value(s) change. Params: %{"id" => dom_id, "value" => list}. See Events in the module doc. Defaults to nil.

  • on_value_change_client (:string) - Browser CustomEvent type when the open value(s) change. event.detail: %{id: dom_id, value: list, previousValue: list, added: list, removed: list} (TS: AccordionChangedDetail). See Events in the module doc. Defaults to nil.

  • on_focus_change (:string) - LiveView event name for pushEvent when the focused item changes. Params: %{"id" => dom_id, "value" => focused_item_value}. See Events in the module doc. Defaults to nil.

  • on_focus_change_client (:string) - Browser CustomEvent type for focus changes. event.detail: %{id: dom_id, value: focused_value}. See Events in the module doc. Defaults to nil.

  • Global attributes are accepted.

Slots

  • inner_block - Compound mode inner content. Use with the compound attribute and :let={ctx}. ctx is 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 panel and a matching value on :trigger and :content. Accepts attributes:

    • value (:string)
    • class (:string)
  • trigger - With :items, optional custom trigger; use :let={item}. Without :items (manual mode), one slot per panel with value (or default item-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 panel with value (or default item-0, …). Accepts attributes:

    • value (:string)
    • class (:string)
    • disabled (:boolean)

accordion_skeleton(assigns)

Renders a loading skeleton for the accordion component.

Attributes

  • count (:integer) - Defaults to 3.
  • Global attributes are accepted.

Slots

  • trigger - Accepts attributes:
    • class (:string)
  • indicator - Accepts attributes:
    • class (:string)
  • content - Accepts attributes:
    • class (:string)

Compounds

accordion_content(assigns)

Renders the content panel 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 accordion animation from compound ctx. Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block (required)

accordion_indicator(assigns)

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)

accordion_item(assigns)

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 to false.
  • Global attributes are accepted.

Slots

  • inner_block

accordion_root(assigns)

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)

accordion_trigger(assigns)

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

focused(accordion_id)

Requests the accordion's focused item value from the browser. Returns a Phoenix.LiveView.JS command.

Options: :respond_to:server (default, accordion_focused_response only), :both (also dispatches accordion-focused), or :client (accordion-focused DOM event only).

Examples

From Client Binding

<.action phx-click={Corex.Accordion.focused("my-accordion")} class="button button--sm">
  Focused
</.action>
<.action phx-click={Corex.Accordion.focused("my-accordion", respond_to: :client)} class="button button--sm">
  Focused (client only)
</.action>

<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
  %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
  %{value: "duis", trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula, at interdum tellus."}
])} />

```javascript
const el = document.getElementById("my-accordion");
el?.addEventListener("accordion-focused", (e) => console.log(e.detail));
```

JS.dispatch

<.action
  phx-click={JS.dispatch("corex:accordion:focused",
    to: "#my-accordion",
    detail: %{respond_to: "client"},
    bubbles: false
  )}
  class="button button--sm"
>
  Focused (JS.dispatch, client only)
</.action>

focused(socket, accordion_id, opts \\ [])

Requests the accordion's focused item value from the client. Pushes a LiveView event handled by the hook.

See focused/2 for :respond_to.

Examples

def handle_event("accordion_focused_response", %{"id" => id, "value" => value}, socket) do
  {:noreply, assign(socket, :accordion_focused, {id, value})}
end

item_state(accordion_id, item_value)

Zag getItemState for one item. Pass value and keyword opts (:disabled, :respond_to).

  • item_state/2item_state(id, value); same as item_state(id, value, []).
  • item_state/3 — client: item_state(id, value, opts). Server: item_state(socket, id, value) or item_state(socket, id, value, opts).

The hook receives value and disabled, calls getItemState, pushes accordion_item_state_response and/or dispatches accordion-item-state with detail: %{id, value, state} according to :respond_to.

Examples

From Client Binding

<.action phx-click={Corex.Accordion.item_state("my-accordion", "lorem", disabled: false)} class="button button--sm">
  item_state("lorem")
</.action>
<.action phx-click={Corex.Accordion.item_state("my-accordion", "donec", disabled: true)} class="button button--sm">
  item_state("donec", disabled)
</.action>

<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
  %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
  %{value: "donec", trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a. Sed ac eros luctus."}
])} />

From Client JS

```javascript
const el = document.getElementById("my-accordion");
el?.addEventListener("accordion-item-state", (e) => console.log(e.detail));
```

<.action
  phx-click={JS.dispatch("corex:accordion:item-state",
    to: "#my-accordion",
    detail: %{value: "lorem", disabled: false, respond_to: "client"},
    bubbles: false
  )}
  class="button button--sm"
>
  item_state("lorem") (JS.dispatch, client only)
</.action>

From Server

def handle_event(
      "accordion_item_state_response",
      %{"id" => id, "value" => v, "state" => state},
      socket
    ) do
  {:noreply, assign(socket, :accordion_item_state, {id, v, state})}
end

set_value(accordion_id, value)

Sets the accordion value from client-side. Returns a Phoenix.LiveView.JS command.

Pass a list of open item ids, or a comma-separated binary (trimmed); an empty binary closes all panels.

Examples

From Client Binding

<.action phx-click={Corex.Accordion.set_value("my-accordion", ["lorem"])} class="button button--sm">
  Open Lorem
</.action>
<.action phx-click={Corex.Accordion.set_value("my-accordion", ["lorem", "donec"])} class="button button--sm">
  Open Lorem and Donec
</.action>
<.action phx-click={Corex.Accordion.set_value("my-accordion", [])} class="button button--sm">
  Close all
</.action>

<.accordion
  id="my-accordion"
  class="accordion"
  items={
    Corex.Content.new([
      %{
        value: "lorem",
        trigger: "Lorem ipsum dolor sit amet",
        content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
      },
      %{
        value: "duis",
        trigger: "Duis dictum gravida odio ac pharetra?",
        content: "Nullam eget vestibulum ligula, at interdum tellus."
      },
      %{
        value: "donec",
        trigger: "Donec condimentum ex mi",
        content: "Congue molestie ipsum gravida a. Sed ac eros luctus.",
        disabled: true
      }
    ])
  }
/>

From Client JS

```javascript
const el = document.getElementById("my-accordion");
const set = (value) =>
  el?.dispatchEvent(
    new CustomEvent("corex:accordion:set-value", {
      bubbles: false,
      detail: { value },
    })
  );

set(["lorem"]);
set(["lorem", "donec"]);
set([]);
```

set_value(socket, accordion_id, value)

Sets the accordion value from server-side. Pushes a LiveView event.

Examples

Heex

<.action phx-click="open_lorem" class="button button--sm">
  Open Lorem
</.action>
<.action phx-click="open_lorem_and_donec" class="button button--sm">
  Open Lorem and Donec
</.action>
<.action phx-click="close_all" class="button button--sm">
  Close all
</.action>

<.accordion
  id="my-accordion"
  class="accordion"
  items={
    Corex.Content.new([
      %{
        value: "lorem",
        trigger: "Lorem ipsum dolor sit amet",
        content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."
      },
      %{
        value: "duis",
        trigger: "Duis dictum gravida odio ac pharetra?",
        content: "Nullam eget vestibulum ligula, at interdum tellus."
      },
      %{
        value: "donec",
        trigger: "Donec condimentum ex mi",
        content: "Congue molestie ipsum gravida a. Sed ac eros luctus.",
        disabled: true
      }
    ])
  }
/>

Elixir

def handle_event("open_lorem", _params, socket) do
  {:noreply, Corex.Accordion.set_value(socket, "my-accordion", ["lorem"])}
end

def handle_event("open_lorem_and_donec", _params, socket) do
  {:noreply, Corex.Accordion.set_value(socket, "my-accordion", ["lorem", "donec"])}
end

def handle_event("close_all", _params, socket) do
  {:noreply, Corex.Accordion.set_value(socket, "my-accordion", [])}
end

value(accordion_id)

Requests the accordion's current open values from the browser. Returns a Phoenix.LiveView.JS command.

Options:

  • :respond_to:server (default, LiveView accordion_value_response only), :both (also dispatches accordion-value), or :client (DOM accordion-value only). When :server and the LiveView is not connected, nothing is pushed.

The hook pushes accordion_value_response when :respond_to is :both or :server, and dispatches accordion-value on the element when :respond_to is :both or :client.

Examples

From Client Binding

<.action phx-click={Corex.Accordion.value("my-accordion")} class="button button--sm">
  Value
</.action>
<.action phx-click={Corex.Accordion.value("my-accordion", respond_to: :client)} class="button button--sm">
  Value (client only)
</.action>

<.accordion id="my-accordion" class="accordion" items={Corex.Content.new([
  %{value: "lorem", trigger: "Lorem ipsum dolor sit amet", content: "Consectetur adipiscing elit. Sed sodales ullamcorper tristique."},
  %{value: "duis", trigger: "Duis dictum gravida odio ac pharetra?", content: "Nullam eget vestibulum ligula, at interdum tellus."},
  %{value: "donec", trigger: "Donec condimentum ex mi", content: "Congue molestie ipsum gravida a. Sed ac eros luctus."}
])} />

```javascript
const el = document.getElementById("my-accordion");
el?.addEventListener("accordion-value", (e) => console.log(e.detail));
```

JS.dispatch

<.action
  phx-click={JS.dispatch("corex:accordion:value",
    to: "#my-accordion",
    detail: %{respond_to: "client"},
    bubbles: false
  )}
  class="button button--sm"
>
  Value (JS.dispatch, client only)
</.action>

value(socket, accordion_id, opts \\ [])

Requests the accordion's current open values from the client. Pushes a LiveView event handled by the hook.

See value/2 for :respond_to. The hook pushes accordion_value_response and/or dispatches accordion-value accordingly.

Examples

def handle_event("accordion_value_response", %{"id" => id, "value" => value}, socket) do
  {:noreply, assign(socket, :accordion_value, {id, value})}
end

Functions

focused(accordion_id, opts)

item_state(accordion_id, item_value, opts)

item_state(socket, accordion_id, item_value, opts)

value(accordion_id, opts)