# dioxus-tabular
[](https://github.com/ryo33/dioxus-tabular)

[](https://crates.io/crates/dioxus-tabular)
[](https://docs.rs/dioxus-tabular)

**Type-safe and composable table framework for Dioxus.**
Define self-contained columns with rendering, filtering, and sorting logic that work independently of table data types and components.
## Version compatibility
```toml
# for Dioxus 0.6.3
dioxus-tabular = "0.1"
# for Dioxus 0.7.0
dioxus-tabular = "0.2"
```
## Overview
`dioxus-tabular` is a Dioxus-native framework for building **structured, declarative, and strongly-typed table UIs**.
Instead of configuring your table with dynamic descriptors or ad-hoc data models, you define **columns as typed components** that don't depend on the actual table data type or table component.
Each column owns its own rendering, filtering, and sorting logic — and can hold local reactive state via `Signal`.
This approach may seem more verbose initially, but it enables:
- **Self-contained, type-safe column definitions** that work with any compatible row type
- **Interchangeable columns, table data types, and table components**
- **Declarative multi-column sorting and filtering**
- **Column reordering, hiding, and visibility control**
- **Table export** to various formats (CSV, Excel, etc.)
- **Extensible abstractions** (`Row`, `GetRowData`, `TableColumn`)
## Design Philosophy
- Columns are *first-class citizens*: each is a self-contained logic unit.
- Columns are *composable*: they can be freely combined into tables.
- Columns are *type-safe*: If a column type does not fit the row data type, the compiler will complain.
- Columns are *independent*: they define their own state, filtering, sorting, and rendering logic.
- Columns, data, and tables are *reusable* and *interchangeable*: They don't depend on each other, so you can freely mix and match them.
## Core Concepts
| `Row` | Defines the unique key and identity of each row. |
| `GetRowData<T>` | Provides access to the data of the row by a specific type. |
| `TableColumn` | Describes how a single column renders, filters, and compares rows. |
| `Columns` | A composed collection of `TableColumn`s, implemented for tuples. |
## Features
### Multi-Column Sorting
Tables support declarative multi-column sorting with priority control:
- Each column can define its own comparison logic via `TableColumn::compare()`
- Each column can request:
- sort direction (ascending or descending) or toggle the direction
- sort priority (primary or last)
- sort removal
- All sort requests are applied automatically when rendering rows
### Row Filtering
Columns can implement custom filtering logic:
- Each column defines its own `TableColumn::filter()` method
- All column filters are automatically applied when rendering rows
### Column Ordering and Visibility
Control which columns are displayed and in what order:
- **Hide/Show**: `hide_column()`, `show_column()` - Toggle column visibility
- **Reorder**: `move_to()`, `swap_columns()` - Change column positions
- **Navigate**: `move_forward()`, `move_backward()` - Move columns incrementally
- **Reset**: `reset_column_order()` - Restore default order and visibility
Access these methods through `TableContextData` or `ColumnContext`.
### Export to various formats (requires the optional `export` feature)
You can export table data with your custom exporter implementation. Enable the `export` feature, and implement the `SerializableColumn` trait for your columns and the `Exporter` trait for your exporter.
See the [example](examples/export.rs) for more details.
## Example scenario
You can define types and implement traits as follows:
Rows:
- `User` implements `Row`, `GetRowData<UserId>` and `GetRowData<UserName>`
- `AccessLog` implements `Row`, `GetRowData<AccessLogId>`, `GetRowData<Timestamp>` and `GetRowData<UserId>`
Columns:
- `UserIdColumn` implements `TableColumn<T>` for every `T where GetRowData<UserId>`
- `UserNameColumn` implements `TableColumn<T>` for every `T where GetRowData<UserName>`
- `AccessLogIdColumn` implements `TableColumn<T>` for every `T where GetRowData<AccessLogId>`
- `TimestampColumn` implements `TableColumn<T>` for every `T where GetRowData<Timestamp>`
And, you define a simple table component like the following:
```rust
#[component]
pub fn SimpleTable<R: Row, C: Columns<R>>(rows: ReadSignal<Vec<R>>, columns: C) -> Element {
let table_context = TableContext::use_table_context(columns.column_names());
rsx! {
table {
thead {
tr { {columns.render_headers(table_context)} }
}
tbody {
for row in rows.iter() {
tr { key: "{row.key().into()}",
{columns.render_columns(table_context, &row, vec![])}
}
}
}
}
}
}
```
and another one:
```rust
#[component]
pub fn FancyTable<R: Row, C: Columns<R>>(rows: ReadSignal<Vec<R>>, columns: C) -> Element {
// Another table component with different styling or features than the above one.
// ...
}
```
You can then render various kinds of tables with different column combinations:
```rust
let users: Vec<User> = ...;
let access_logs: Vec<AccessLog> = ...;
rsx! {
// Simple table with two columns for showing user ids and names.
SimpleTable { rows: users, columns: (UserIdColumn, UserNameColumn) }
// Same data and columns, but with different styling or features than the above one.
FancyTable { rows: users, columns: (UserIdColumn, UserNameColumn) }
// Table for access logs. The UserIdColumn is reusable across different table types.
SimpleTable { rows: access_logs, columns: (AccessLogIdColumn, TimestampColumn, UserIdColumn) }
}
```
Also, if you implement filtering and sorting logic in the columns, any tables have sorting and filtering features without any additional code to each table.
## License
MIT or Apache 2.0 at your option.