Overflow List

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless overflow_list
Source View Source

Overview of the Phoenix and LiveView Headless Overflow List Component

The Mishka Chelekom Headless Overflow List lays items out on one row in Phoenix and LiveView and folds the ones that do not fit into a +N counter. It renders a div that carries the OverflowList JavaScript hook, one span for each item slot, and a counter. A ResizeObserver re-runs the layout whenever the container width changes. The generator copies overflow_list.js into assets/vendor and wires the hook in.

Items that do not fit get data-hidden and inert, which removes them from the tab order and the accessibility tree. The component does not hide them for you: add a rule such as [&_[data-part=item][data-hidden]]:hidden to the root classes. The counter starts with data-hidden and is revealed when something overflows, with the hidden count written into its counter-value part. Order is priority, so the last items fold first, and min_visible (1 by default) is how many always stay. id is required because the hook attaches to it. Class attributes: class, item_class, counter_class and counter_value_class.

Base UI Overflow List example - tags with a +N counter (Phoenix headless)

Base UI Overflow List example - tags with a +N counter (Phoenix headless)

DesignPhoenixElixirLiveViewTailwindHeadless +0
heex
<div class="w-72 rounded-lg border border-neutral-300 bg-white p-2 dark:border-neutral-700 dark:bg-neutral-900">
  <.overflow_list
    id="baseui-overflow-list"
    min_visible={1}
    class={
      "flex items-center gap-2 overflow-hidden " <>
        "[&_[data-part=item][data-hidden]]:hidden [&_[data-part=item]]:shrink-0 " <>
        "[&_[data-part=counter][data-hidden]]:hidden [&_[data-part=counter]]:shrink-0 [&_[data-part=counter]]:whitespace-nowrap [&_[data-part=counter]]:rounded-full [&_[data-part=counter]]:bg-neutral-100 [&_[data-part=counter]]:px-2.5 [&_[data-part=counter]]:py-0.5 [&_[data-part=counter]]:text-sm [&_[data-part=counter]]:font-medium [&_[data-part=counter]]:text-neutral-600 dark:[&_[data-part=counter]]:bg-neutral-800 dark:[&_[data-part=counter]]:text-neutral-300"
    }
    item_class="whitespace-nowrap rounded-full bg-neutral-100 px-2.5 py-0.5 text-sm text-neutral-700 dark:bg-neutral-800 dark:text-neutral-200"
  >
    <:item>Design</:item>
    <:item>Phoenix</:item>
    <:item>Elixir</:item>
    <:item>LiveView</:item>
    <:item>Tailwind</:item>
    <:item>Headless</:item>
  </.overflow_list>
</div>

Overflow List example - a box you can resize (Phoenix headless)

Overflow List example - a box you can resize (Phoenix headless)

DesignPhoenixElixirLiveViewTailwindHeadlessAccessibilityComponentsMishkaChelekom +0
heex
<div class="w-[26rem] min-w-28 max-w-full resize-x overflow-auto rounded-lg border border-neutral-300 bg-white p-2 dark:border-neutral-700 dark:bg-neutral-900">
  <.overflow_list
    id="resizable-overflow-list"
    min_visible={1}
    class={@row_classes}
    item_class="whitespace-nowrap rounded-full bg-neutral-100 px-2.5 py-0.5 text-sm text-neutral-700 dark:bg-neutral-800 dark:text-neutral-200"
  >
    <:item :for={tag <- @tags}>{tag}</:item>
  </.overflow_list>
</div>

Reacting on the server

on_change names a LiveView event. The hook pushes it with %{"hidden" => n} when the hidden count changes. The push is debounced, so dragging a resize handle does not flood the server.

heex
<.overflow_list id="tags" on_change="overflow" class={@row_classes} item_class="...">
  <:item :for={tag <- @tags}>{tag}</:item>
</.overflow_list>
<p>Hidden on the server: {@hidden}</p>
elixir
def handle_event("overflow", %{"hidden" => hidden}, socket) do
  {:noreply, assign(socket, hidden: hidden)}
end

Parts & data attributes

The root carries chelekom-overflow-list, data-min-visible and data-on-change. Each part has a data-part value and a chelekom-overflow-list__* class.

  • item is one list item. It gets data-hidden and inert while it overflows.
  • counter is the +N element. It has data-hidden while nothing overflows.
  • counter-value receives the hidden count as text.

Accessibility

There is no formal WAI-ARIA pattern for an overflow list. Overflowing items are made inert, so keyboard and screen reader users reach only what is visible. The counter is plain text, not a control. If people need the hidden items, show them elsewhere using the count from on_change.