Unified collection component for displaying data in table, list, or grid layouts.
This component provides a single API for all collection displays, with the layout
attribute selecting how items are rendered. All layouts share the same filtering,
sorting, pagination, and URL sync capabilities.
Layouts
:table(default) - Traditional HTML table with sortable headers:list- Vertical list with sort button group:grid- Responsive card grid with sort button group
Basic Usage
Table Layout (default)
<Cinder.collection resource={MyApp.User} actor={@current_user}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.collection>List Layout
<Cinder.collection resource={MyApp.User} actor={@current_user} layout={:list}>
<:col field="name" filter sort />
<:col field="status" filter={:select} />
<:item :let={user}>
<div class="flex justify-between">
<span class="font-bold">{user.name}</span>
<span>{user.status}</span>
</div>
</:item>
</Cinder.collection>Grid Layout
<Cinder.collection resource={MyApp.Product} actor={@current_user} layout={:grid}>
<:col field="name" filter sort search />
<:col field="category" filter={:select} />
<:item :let={product}>
<h3 class="font-bold">{product.name}</h3>
<p class="text-gray-600">{product.category}</p>
<p class="text-lg font-semibold">${product.price}</p>
</:item>
</Cinder.collection>Grid Columns
Control the number of grid columns with the grid_columns attribute:
<!-- Fixed 4 columns -->
<Cinder.collection resource={MyApp.Product} layout={:grid} grid_columns={4}>
...
</Cinder.collection>
<!-- Responsive columns -->
<Cinder.collection resource={MyApp.Product} layout={:grid} grid_columns={[xs: 1, md: 2, lg: 3, xl: 4]}>
...
</Cinder.collection>Layout-Specific Attributes
| Attribute | Table | List | Grid | Description |
|---|---|---|---|---|
<:col> content | ✅ Rendered | ❌ Ignored | ❌ Ignored | Cell content for table rows |
<:item> slot | ❌ Ignored | ✅ Required | ✅ Required | Template for each item |
sort_label | ❌ N/A | ✅ Button label | ✅ Button label | Label for sort button group |
container_class | ❌ N/A | ✅ Override | ✅ Override | Custom container CSS |
grid_columns | ❌ N/A | ❌ N/A | ✅ Column count | Number of grid columns |
click | ✅ Row click | ✅ Item click | ✅ Item click | Click handler |
Custom Controls Layout
Use the :controls slot to customize how filters and search are rendered while
keeping Cinder's state management, URL sync, and query building intact:
<Cinder.collection resource={MyApp.User} actor={@current_user}>
<:col :let={user} field="name" filter sort search>{user.name}</:col>
<:col :let={user} field="status" filter={:select}>{user.status}</:col>
<:controls :let={controls}>
<Cinder.Controls.render_header {controls} />
<div class="grid grid-cols-2 gap-4">
<Cinder.Controls.render_filter
:for={filter <- controls.filters}
filter={filter}
/>
</div>
</:controls>
</Cinder.collection>See Cinder.Controls for the full API and more examples.
Summary
Functions
Builds the list of columns used for query operations (filtering AND searching).
Process column definitions into the format expected by the underlying component.
Process filter-only slot definitions into the format expected by the filter system.
Process unified search configuration into individual components. Returns {label, placeholder, enabled, search_fn}.
Functions
Builds the list of columns used for query operations (filtering AND searching).
This function combines:
- Filterable columns: needed for filter application
- Searchable columns: needed for search even if not filterable
- Filter-only slots: dedicated filter controls not tied to display columns
NOTE: Sortable-only columns are NOT included here - sorting uses display_columns because sort controls are tied to column headers.
Raises ArgumentError if the same field is defined in both a filterable column
and a filter-only slot.
Attributes
resource(:atom) - The Ash resource to query (use either resource or query, not both). Defaults tonil.query(:any) - The Ash query to execute (use either resource or query, not both). Defaults tonil.action(:atom) - The read action to use. Defaults to the primary read action. Defaults tonil.actor(:any) - Actor for authorization. Defaults tonil.tenant(:any) - Tenant for multi-tenant resources. Defaults tonil.scope(:any) - Ash scope containing actor and tenant. Defaults tonil.layout(:any) - Layout type: :table, :list, or :grid (also accepts strings). Defaults to:table.id(:string) - Unique identifier for the collection. Defaults to"cinder-collection".page_size(:any) - Number of items per page or [default: 25, options: [10, 25, 50]]. SeeCinder.PageSizefor global configuration. Defaults tonil.theme(:any) - Theme name or theme map. Defaults to"default".url_state(:any) - URL state object from UrlSync.handle_params, or false to disable. Defaults tofalse.query_opts(:list) - Additional Ash query options. Defaults to[].on_state_change(:any) - Custom state change handler. Defaults tonil.show_pagination(:boolean) - Whether to show pagination controls. Defaults totrue.pagination(:any) - Pagination mode: :offset (default) or :keyset. Keyset pagination is faster for large datasets but only supports prev/next navigation. Defaults to:offset.show_filters(:any) - Controls filter visibility. true = always visible, false = hidden, nil = auto-detect, :toggle/"toggle" = collapsible starting collapsed, :toggle_open/"toggle_open" = collapsible starting expanded. Can also be set globally viaconfig :cinder, show_filters: :toggle. Defaults tonil.show_sort(:boolean) - Whether to show sort controls (auto-detected if nil, list/grid only). Defaults tonil.loading_message(:string) - Message to show while loading. Defaults to"Loading...".filters_label(:string) - Label for the filters component (defaults to translated "Filters"). Defaults tonil.sort_label(:string) - Label for sort button group (defaults to translated "Sort by:"). Defaults tonil.search(:any) - Search configuration. Auto-enables when searchable columns exist. Defaults tonil.empty_message(:string) - Message to show when no results. Defaults tonil.error_message(:string) - Message to show on error. Defaults tonil.class(:string) - Additional CSS classes for the outer container. Defaults to"".container_class(:string) - Override the item container CSS class (list/grid only). Defaults tonil.grid_columns(:any) - Number of grid columns. Integer (e.g., 4) or keyword list (e.g., [xs: 1, md: 2, lg: 3]). Defaults to[xs: 1, md: 2, lg: 3].click(:any) - Function to call when a row/item is clicked. Receives the item as argument. Defaults tonil.id_field(:atom) - Field to use as ID for update_if_visible operations (defaults to :id). Defaults to:id.selectable(:boolean) - Enable row/item selection via checkboxes. Defaults tofalse.on_selection_change(:any) - Event name (atom or string) sent to parent when selection changes. Parent receives {event_name, %{selected_ids: MapSet.t(), selected_count: integer(), component_id: string(), action: atom()}}. Defaults tonil.
Slots
col- Accepts attributes:field(:string) - Field name (supports dot notation for relationships or__for embedded attributes).filter(:any) - Enable filtering (true, false, filter type atom, or unified config).filter_options(:list) - Custom filter options - DEPRECATED: Use filter={[type: :select, options: [...]]} instead.sort(:any) - Enable sorting (true, false, or unified config [cycle: [nil, :asc, :desc]]).search(:boolean) - Enable global search on this column.label(:string) - Custom column label (auto-generated if not provided).class(:string) - CSS classes for table column (table layout only).
item- Template for rendering each item (required for list/grid layouts).filter- Filter-only slots for filtering without display columns. Accepts attributes:field(:string) (required) - Field name to filter on.type(:atom) - Filter type (:text, :select, :boolean, :date_range, etc.).label(:string) - Custom filter label.options(:list) - Options for select/multi_select filters.value(:any) - Default or fixed value for checkbox filters.operator(:atom) - Filter operator (:eq, :contains, :gt, etc.).case_sensitive(:boolean) - Whether text filtering is case-sensitive.placeholder(:string) - Placeholder text for filter input.labels(:map) - Custom labels for boolean filter options.prompt(:string) - Prompt text for select filters.match_mode(:atom) - Match mode for multi-value filters (:any or :all).format(:string) - Date format for date filters.include_time(:boolean) - Whether to include time in date filters.step(:any) - Step value for number range filters.min(:any) - Minimum value for range filters.max(:any) - Maximum value for range filters.fn(:fun) - Custom filter function (fn query, filter_config -> query).
bulk_action- Accepts attributes:action(:any) (required) - Ash action atom or function/2 receiving (query, opts) like code interface functions.label(:string) - Button label text. Supports {count} interpolation. If provided, renders a themed button.variant(:atom) - Button style: :primary (solid), :secondary (outline), :danger (destructive). Default: :primary. Must be one of:primary,:secondary, or:danger.action_opts(:list) - Additional options passed to the Ash action (e.g., [return_records?: true, notify?: true]).confirm(:string) - Confirmation message. Supports {count} interpolation for selected count.on_success(:atom) - Message name sent to parent via handle_info on success. Payload: %{component_id, action, count, result}.on_error(:atom) - Message name sent to parent via handle_info on error. Payload: %{component_id, action, reason}.
controls- Custom layout for the filter/search controls area. Receives a controls data map via :let with filters, search, and metadata. Use Cinder.Controls helpers to render individual filters, search, and headers.loading- Custom loading state content.empty- Custom empty state content.error- Custom error state content.
Process column definitions into the format expected by the underlying component.
Process filter-only slot definitions into the format expected by the filter system.
Process unified search configuration into individual components. Returns {label, placeholder, enabled, search_fn}.