Skip to main content
The Table component displays data in rows and columns with support for sorting, hover effects, and custom column widths.

Basic Usage

use freya::prelude::*;

fn app() -> impl IntoElement {
    Table::new()
        .child(
            TableHead::new().child(
                TableRow::new()
                    .child(TableCell::new().child("Name"))
                    .child(TableCell::new().child("Age"))
                    .child(TableCell::new().child("City"))
            )
        )
        .child(
            TableBody::new()
                .child(
                    TableRow::new()
                        .child(TableCell::new().child("Alice"))
                        .child(TableCell::new().child("30"))
                        .child(TableCell::new().child("New York"))
                )
                .child(
                    TableRow::new()
                        .child(TableCell::new().child("Bob"))
                        .child(TableCell::new().child("25"))
                        .child(TableCell::new().child("London"))
                )
        )
}

Components

Tables are composed of several components:
  • Table: Container for the entire table
  • TableHead: Contains header row(s)
  • TableBody: Contains data rows
  • TableRow: A single row
  • TableCell: A cell within a row

Properties

Table

height
Size
default:"Size::Inner"
Height of the table container
column_widths
Vec<Size>
Custom widths for each column. Defaults to equal flex distribution
theme
TableThemePartial
Custom theme for the table

TableCell

on_press
EventHandler<Event<PressEventData>>
Callback when cell is clicked (useful for column headers)
order_direction
Option<OrderDirection>
Shows sort arrow: Some(OrderDirection::Up), Some(OrderDirection::Down), or None
padding
Gaps
default:"Gaps::new_all(5.0)"
Padding inside the cell
height
Size
default:"Size::px(35.0)"
Height of the cell

Column Widths

Customize column widths:
Table::new()
    .column_widths([
        Size::px(200.),      // Fixed width
        Size::flex(2.),      // Takes 2x space
        Size::flex(1.),      // Takes 1x space
    ])
    // ... children

Sortable Columns

Implement sorting with clickable headers:
use freya::prelude::*;

#[derive(PartialEq, Clone)]
enum SortBy { Name, Age, City }

fn app() -> impl IntoElement {
    let mut sort_by = use_state(|| SortBy::Name);
    let mut sort_dir = use_state(|| OrderDirection::Down);
    
    let toggle_sort = move |col: SortBy| {
        if *sort_by.read() == col {
            sort_dir.set(match *sort_dir.read() {
                OrderDirection::Down => OrderDirection::Up,
                OrderDirection::Up => OrderDirection::Down,
            });
        } else {
            sort_by.set(col);
            sort_dir.set(OrderDirection::Down);
        }
    };
    
    Table::new()
        .child(
            TableHead::new().child(
                TableRow::new()
                    .child(
                        TableCell::new()
                            .on_press(move |_| toggle_sort(SortBy::Name))
                            .order_direction(
                                (*sort_by.read() == SortBy::Name).then(|| *sort_dir.read())
                            )
                            .child("Name")
                    )
                    .child(
                        TableCell::new()
                            .on_press(move |_| toggle_sort(SortBy::Age))
                            .order_direction(
                                (*sort_by.read() == SortBy::Age).then(|| *sort_dir.read())
                            )
                            .child("Age")
                    )
                    .child(TableCell::new().child("City"))
            )
        )
        .child(
            TableBody::new()
                // ... sorted rows
        )
}

Scrollable Table

Combine with ScrollView for large datasets:
Table::new()
    .child(
        TableHead::new().child(
            TableRow::new()
                .child(TableCell::new().child("Column 1"))
                .child(TableCell::new().child("Column 2"))
        )
    )
    .child(
        TableBody::new().child(
            ScrollView::new()
                .height(Size::px(400.))
                .children(
                    (0..100).map(|i| {
                        TableRow::new()
                            .key(i)
                            .child(TableCell::new().child(format!("Row {} Col 1", i)))
                            .child(TableCell::new().child(format!("Row {} Col 2", i)))
                            .into()
                    })
                )
        )
    )

Complete Example

Full sortable table with data:
use freya::prelude::*;
use itertools::Itertools;

#[derive(PartialEq, Clone)]
enum OrderBy { Name, Type, Rank }

fn app() -> impl IntoElement {
    let mut order = use_state(|| OrderBy::Name);
    let mut order_direction = use_state(|| OrderDirection::Down);
    
    let data = vec![
        vec!["Zeus", "Sky", "01"],
        vec!["Poseidon", "Sea", "03"],
        vec!["Ares", "War", "08"],
        vec!["Aphrodite", "Love", "10"],
    ];
    
    let sorted_data = data.iter().sorted_by(|a, b| {
        let cmp = match *order.read() {
            OrderBy::Name => Ord::cmp(a[0], b[0]),
            OrderBy::Type => Ord::cmp(a[1], b[1]),
            OrderBy::Rank => Ord::cmp(a[2], b[2]),
        };
        if *order_direction.read() == OrderDirection::Down {
            cmp
        } else {
            cmp.reverse()
        }
    });
    
    Table::new()
        .column_widths([Size::flex(4.), Size::flex(3.), Size::flex(1.)])
        .child(
            TableHead::new().child(
                TableRow::new()
                    .child(
                        TableCell::new()
                            .on_press(move |_| toggle_sort(&order, &order_direction, OrderBy::Name))
                            .order_direction((*order.read() == OrderBy::Name).then(|| *order_direction.read()))
                            .child("Name")
                    )
                    .child(
                        TableCell::new()
                            .on_press(move |_| toggle_sort(&order, &order_direction, OrderBy::Type))
                            .order_direction((*order.read() == OrderBy::Type).then(|| *order_direction.read()))
                            .child("Type")
                    )
                    .child(
                        TableCell::new()
                            .on_press(move |_| toggle_sort(&order, &order_direction, OrderBy::Rank))
                            .order_direction((*order.read() == OrderBy::Rank).then(|| *order_direction.read()))
                            .child("Rank")
                    )
            )
        )
        .child(
            TableBody::new().children(
                sorted_data.map(|row| {
                    TableRow::new()
                        .child(TableCell::new().child(row[0]))
                        .child(TableCell::new().child(row[1]))
                        .child(TableCell::new().child(row[2]))
                        .into()
                })
            )
        )
}

Row Hover Effect

Rows automatically show a hover background when the mouse is over them.

Theming

Customize table appearance:
Table::new()
    .theme(TableThemePartial {
        background: Some(Color::WHITE),
        row_background: Some(Color::from_rgb(250, 250, 250)),
        hover_row_background: Some(Color::from_rgb(240, 240, 255)),
        divider_fill: Some(Color::from_rgb(200, 200, 200)),
        ..Default::default()
    })

Source

View the full implementation: table.rs

Build docs developers (and LLMs) love