Cinder.Table (Cinder v0.7.2)
View SourceSimplified Cinder table component with intelligent defaults.
This is the new, simplified API for Cinder tables that leverages automatic type inference and smart defaults while providing a clean, Phoenix LiveView-like interface.
Basic Usage
With Resource Parameter (Simple)
<Cinder.Table.table 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>
<:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>
With Query Parameter (Advanced)
<!-- Using resource as query -->
<Cinder.Table.table query={MyApp.User} actor={@current_user}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
<:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>
<!-- Pre-configured query with custom read action -->
<Cinder.Table.table query={Ash.Query.for_read(MyApp.User, :active_users)} actor={@current_user}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
<:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>
<!-- Query with base filters -->
<Cinder.Table.table query={MyApp.User |> Ash.Query.filter(department: "Engineering")} actor={@current_user}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
<:col :let={user} field="department.name" filter>{user.department.name}</:col>
<:col :let={user} field="profile__country" filter>{user.profile.country}</:col>
</Cinder.Table.table>
Field Types
Relationship Fields
Use dot notation to access related resource fields:
<:col :let={user} field="department.name" filter sort>{user.department.name}</:col>
<:col :let={user} field="manager.email" filter>{user.manager.email}</:col>
<:col :let={user} field="office.building.address" filter>{user.office.building.address}</:col>
Embedded Resource Fields
Use double underscore notation for embedded resource fields:
<:col :let={user} field="profile__bio" filter>{user.profile.bio}</:col>
<:col :let={user} field="settings__country" filter>{user.settings.country}</:col>
<:col :let={user} field="metadata__preferences__theme" filter>{user.metadata.preferences.theme}</:col>
Embedded enum fields are automatically detected and rendered as select filters:
<!-- If profile.country is an Ash.Type.Enum, this becomes a select filter -->
<:col :let={user} field="profile__country" filter>{user.profile.country}</:col>
Search Configuration
Search is automatically enabled when columns have the search
attribute:
<Cinder.Table.table resource={MyApp.Album} actor={@current_user}>
<:col :let={album} field="title" filter search>{album.title}</:col>
<:col :let={album} field="artist.name" filter search>{album.artist.name}</:col>
<:col :let={album} field="genre" filter>{album.genre}</:col>
</Cinder.Table.table>
Customize search label and placeholder:
<Cinder.Table.table
resource={MyApp.Album}
actor={@current_user}
search={[label: "Find Albums", placeholder: "Search by title or artist..."]}
>
<:col :let={album} field="title" search>{album.title}</:col>
<:col :let={album} field="artist.name" search>{album.artist.name}</:col>
</Cinder.Table.table>
Explicitly disable search even with searchable columns:
<Cinder.Table.table resource={MyApp.Album} search={false}>
<:col :let={album} field="title" search>{album.title}</:col>
</Cinder.Table.table>
Advanced Configuration
<Cinder.Table.table
resource={MyApp.Album}
actor={@current_user}
url_state={@url_state}
page_size={50}
theme="modern"
>
<:col :let={album} field="title" filter sort class="w-1/2">
{album.title}
</:col>
<:col :let={album} field="artist.name" filter sort>
{album.artist.name}
</:col>
<:col :let={album} field="genre" filter={:select}>
{album.genre}
</:col>
</Cinder.Table.table>
Configurable Page Sizes
Allow users to select their preferred page size:
<Cinder.Table.table
resource={MyApp.User}
actor={@current_user}
page_size={[default: 25, options: [10, 25, 50, 100]]}
>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
<:col :let={user} field="department.name" filter sort>{user.department.name}</:col>
</Cinder.Table.table>
The page size selector appears automatically when multiple options are provided.
For fixed page sizes, use: page_size={25}
Complex Query Examples
<!-- Admin interface with authorization and tenant -->
<Cinder.Table.table
query={MyApp.User
|> Ash.Query.for_read(:admin_read, %{}, actor: @actor, authorize?: @authorizing)
|> Ash.Query.set_tenant(@tenant)
|> Ash.Query.filter(active: true)}
actor={@actor}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
<:col :let={user} field="last_login" sort>{user.last_login}</:col>
<:col :let={user} field="role" filter={:select}>{user.role}</:col>
</Cinder.Table.table>
Multi-Tenant Examples
<!-- Simple tenant support -->
<Cinder.Table.table
resource={MyApp.User}
actor={@current_user}
tenant={@tenant}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>
<!-- Using Ash scope (only actor and tenant are extracted) -->
<Cinder.Table.table
resource={MyApp.User}
scope={%{actor: @current_user, tenant: @tenant}}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>
<!-- Custom scope struct -->
<Cinder.Table.table
resource={MyApp.User}
scope={@my_scope}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>
<!-- Mixed usage (explicit overrides scope) -->
<Cinder.Table.table
resource={MyApp.User}
scope={@scope}
actor={@different_actor}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
</Cinder.Table.table>
Features
- Automatic type inference from Ash resources
- Intelligent filtering with automatic filter type detection
- URL state management with browser back/forward support
- Relationship support using dot notation (e.g.,
artist.name
) - Flexible theming with built-in presets
Summary
Functions
Renders a data table with intelligent defaults.
Functions
Renders a data table with intelligent defaults.
Attributes
Resource/Query (Choose One)
resource
- Ash resource module to query (use either resource or query, not both)query
- Ash query to execute (use either resource or query, not both)
Required
actor
- Actor for authorization (can be nil)
Authorization & Tenancy
tenant
- Tenant for multi-tenant resources (default: nil)scope
- Ash scope containing actor and tenant (default: nil)
Optional Configuration
id
- Component ID (defaults to "cinder-table")page_size
- Number of items per page (default: 25)theme
- Theme preset or custom theme map (default: "default")url_state
- URL state object from UrlSync.handle_params, or false to disable URL synchronizationquery_opts
- Additional query options for Ash (default: [])on_state_change
- Callback for state changesshow_filters
- Show filter controls (default: auto-detect from columns)show_pagination
- Show pagination controls (default: true)loading_message
- Custom loading messagefilters_label
- Custom label for filtering (default: "🔍 Filters")empty_message
- Custom empty state messageclass
- Additional CSS classes
When to Use Resource vs Query
Use resource
for:
- Simple tables with default read actions
- Getting started quickly
- Standard use cases without custom requirements
Use query
for:
- Custom read actions (e.g.,
:active_users
,:admin_only
) - Pre-filtering data with base filters
- Custom authorization settings
- Tenant-specific queries
- Admin interfaces with complex requirements
- Integration with existing Ash query pipelines
Column Slot
The :col
slot supports these attributes:
field
(required) - Field name or relationship path (e.g., "user.name")filter
- Enable filtering (boolean or filter type atom)sort
- Enable sorting (boolean)class
- CSS classes for this columnlabel
- Column header label (auto-generated from field name if not provided)
Filter types: :text
, :select
, :multi_select
, :multi_checkboxes
, :boolean
, :checkbox
, :date_range
, :number_range
Filter Type Selection:
:multi_select
- Modern tag-based interface with dropdown (default for array types)- Supports
match_mode: :any
(default) for OR logic ormatch_mode: :all
for AND logic
- Supports
:multi_checkboxes
- Traditional checkbox interface for multiple selection- Supports
match_mode: :any
(default) for OR logic ormatch_mode: :all
for AND logic
- Supports
:boolean
- Radio buttons for true/false selection (default for boolean fields):checkbox
- Single checkbox for "show only X" filtering- For boolean fields: defaults to filtering for
true
when checked - For non-boolean fields: requires explicit
value
option
- For boolean fields: defaults to filtering for
Filter Slot
The :filter
slot allows filtering on fields that are not displayed in the table:
<:filter field="created_at" type="date_range" label="Creation Date" />
<:filter field="department" type="select" options={["Sales", "Marketing"]} />
The :filter
slot supports these attributes:
Universal attributes (all filter types):
field
(required) - Field name or relationship path (e.g., "user.name")type
- Filter type (e.g.,:select
,:text
,:date_range
) - auto-detected if not providedlabel
- Filter label (auto-generated from field name if not provided)fn
- Custom filter function
Filter type specific attributes:
Text filters (:text
):
operator
- Operator (:contains
,:starts_with
,:ends_with
,:equals
)case_sensitive
- Whether filter should be case sensitiveplaceholder
- Placeholder text for input
Select filters (:select
):
options
- Options list (e.g.,[{"Label", "value"}]
)prompt
- Prompt text ("Choose..." style text)
Multi-select filters (:multi_select
, :multi_checkboxes
):
options
- Options list (e.g.,[{"Label", "value"}]
)match_mode
- Match mode (:any
for OR logic,:all
for AND logic)prompt
- Prompt text (:multi_select
only)
Boolean filters (:boolean
):
labels
- Custom labels (map with:true
,:false
keys)
Checkbox filters (:checkbox
):
value
- Filter value when checkedlabel
- Display text (required for checkbox)
Date range filters (:date_range
):
format
- Format (:date
or:datetime
)include_time
- Whether to include time selection
Number range filters (:number_range
):
step
- Step value for inputsmin
- Minimum allowed valuemax
- Maximum allowed value
Filter-only slots use the same filter types and options as column filters, but are purely for filtering without displaying the field in the table.
Column Labels
Column labels are automatically generated from field names using intelligent humanization:
name
→ "Name"email_address
→ "Email Address"user.name
→ "User Name"created_at
→ "Created At"
You can override the auto-generated label by providing a label
attribute.
Row Click Functionality
Tables can be made interactive by providing a row_click
function that will be
executed when a row is clicked:
<Cinder.Table.table
resource={MyApp.Item}
actor={@current_user}
row_click={fn item -> JS.navigate(~p"/items/#{item.id}") end}
>
<:col :let={item} field="name" filter sort>{item.name}</:col>
<:col :let={item} field="description">{item.description}</:col>
</Cinder.Table.table>
The row_click
function receives the row item as its argument and should return
a Phoenix.LiveView.JS command or similar action. When provided, rows will be
styled to indicate they are clickable with hover effects and cursor changes.
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
.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
.id
(:string
) - Unique identifier for the table. Defaults to"cinder-table"
.page_size
(:any
) - Number of items per page or [default: 25, options: [10, 25, 50]]. Defaults to25
.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 query options (load, select, etc.). Defaults to[]
.on_state_change
(:any
) - Custom state change handler. Defaults tonil
.show_pagination
(:boolean
) - Whether to show pagination controls. Defaults totrue
.show_filters
(:boolean
) - Whether to show filter controls (auto-detected if nil). Defaults tonil
.loading_message
(:string
) - Message to show while loading. Defaults to"Loading..."
.filters_label
(:string
) - Label for the filters component. Defaults to"🔍 Filters"
.search
(:any
) - Search configuration. Auto-enables when searchable columns exist. Use [label: "Custom", placeholder: "Custom..."] to customize, [fn: my_search_fn] for custom search function, or false to disable. Defaults tonil
.empty_message
(:string
) - Message to show when no results. Defaults to"No results found"
.class
(:string
) - Additional CSS classes. Defaults to""
.row_click
(:any
) - Function to call when a row is clicked. Receives the row item as argument. Defaults tonil
.
Slots
col
(required) - Accepts attributes:field
(:string
) - Field name (supports dot notation for relationships or__
for embedded attributes). Required when filter or sort is enabled.filter
(:any
) - Enable filtering (true, false, filter type atom, or unified config [type: :select, options: [...], fn: &custom_filter/2]).filter_options
(:list
) - Custom filter options (e.g., [options: [{"Label", "value"}]]) - 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 (makes column searchable in global search input).label
(:string
) - Custom column label (auto-generated if not provided).class
(:string
) - CSS classes for this column.
filter
- Accepts attributes:field
(:string
) (required) - Field name (supports dot notation for relationships or__
for embedded attributes).type
(:any
) - Filter type as atom or string (e.g., :select, "select", :text, "text", etc.) - auto-detected if not provided.options
(:list
) - Filter options for select/multi-select filters.value
(:any
) - Filter value for checkbox filters.operator
(:atom
) - Text filter operator (:contains, :starts_with, :ends_with, :equals).case_sensitive
(:boolean
) - Whether text filter should be case sensitive.placeholder
(:string
) - Placeholder text for input filters.labels
(:map
) - Custom labels for boolean filter (map with :true, :false keys).prompt
(:string
) - Prompt text for select filters ('Choose...' style text).match_mode
(:atom
) - Multi-select match mode (:any for OR logic, :all for AND logic).format
(:atom
) - Date range format (:date or :datetime).include_time
(:boolean
) - Whether date range should include time selection.step
(:any
) - Step value for number range filters.min
(:any
) - Minimum value for number range filters.max
(:any
) - Maximum value for number range filters.fn
(:any
) - Custom filter function.label
(:string
) - Custom filter label (auto-generated if not provided).