Skip to content

Advanced Components


DataGrid

A feature-rich data table with client-side sorting and pagination. Unlike Table, DataGrid supports per-column sort controls and handles large datasets by rendering only one page at a time.

Signature

DataGrid(data, *, columns, page_size=10)

Parameter Type Default Description
data list[dict] | ReactiveVar | callable Row data
columns list[dict] Column definitions — see below
page_size int 10 Rows per page

Column definition dict

Key Type Required Description
key str Yes Dict key used to look up the row value
label str No Column header text (defaults to key)
sortable bool No Enable client-side sort for this column

Example

users = [
    {"name": "Alice",   "email": "alice@example.com", "status": "Active",   "revenue": 1200},
    {"name": "Bob",     "email": "bob@example.com",   "status": "Inactive", "revenue": 340},
    {"name": "Charlie", "email": "charlie@example.com","status": "Active",   "revenue": 890},
    # … more rows
]

ui.DataGrid(
    data=users,
    columns=[
        {"key": "name",    "label": "Name",    "sortable": True},
        {"key": "email",   "label": "Email"},
        {"key": "status",  "label": "Status",  "sortable": True},
        {"key": "revenue", "label": "Revenue", "sortable": True},
    ],
    page_size=10,
)

Reactive DataGrid

Pass a callable to data to make the grid reactive. The grid re-renders when any reactive variable read inside the callable changes:

search = ui.state("")

def filtered_rows():
    q = search.value.lower()
    return [u for u in all_users if q in u["name"].lower() or q in u["email"].lower()]

ui.TextInput(label="Search", value=search, on_change=search.set)
ui.DataGrid(data=filtered_rows, columns=COLS)

CustomComponent

Base class for building your own components with client-side JavaScript. Override render() to produce HTML, client_script() to add behaviour, and scripts() to declare external script dependencies.

Class interface

from fluxui.components.advanced import CustomComponent

class MyComponent(CustomComponent):
    def render(self) -> str:
        """Return the HTML string for this component."""
        ...

    def client_script(self) -> str:
        """
        Return a JavaScript snippet executed after the component is inserted
        into the DOM.  Use `this` to reference the component's root element.
        """
        return ""

    def scripts(self) -> list[str]:
        """
        Return a list of external <script> src URLs to inject into <head>.
        FluxUI deduplicates them across components.
        """
        return []

How it works

  1. render() produces an HTML string. FluxUI wraps it in a <div data-flux-id="..."> and inserts it into the DOM.
  2. After insertion, FluxUI executes client_script() with this bound to the root element.
  3. When state changes and the component re-renders, client_script() runs again on the new element.

Example — KPI Gauge

import fluxui as ui
from fluxui.components.advanced import CustomComponent


class GaugeChart(CustomComponent):
    def __init__(self, value, *, max_value=100, label=""):
        self._value = value       # ReactiveVar or int
        self._max   = max_value
        self._label = label

    def _resolve(self, v):
        return v.value if hasattr(v, "value") else v

    def render(self) -> str:
        val = self._resolve(self._value)
        return (
            f'<div style="text-align:center">'
            f'  <canvas width="200" height="120"'
            f'          data-value="{val}"'
            f'          data-max="{self._max}"'
            f'          data-label="{self._label}"></canvas>'
            f'</div>'
        )

    def client_script(self) -> str:
        return """
        (function() {
            var canvas = this.querySelector('canvas');
            if (!canvas) return;
            var ctx    = canvas.getContext('2d');
            var value  = parseFloat(canvas.dataset.value);
            var max    = parseFloat(canvas.dataset.max);
            var label  = canvas.dataset.label;
            var pct    = value / max;
            var W = canvas.width, H = canvas.height;
            ctx.clearRect(0, 0, W, H);

            // Background arc
            ctx.beginPath();
            ctx.arc(W/2, H - 10, 80, Math.PI, 0, false);
            ctx.lineWidth = 18;
            ctx.strokeStyle = '#e0e0e0';
            ctx.stroke();

            // Value arc
            ctx.beginPath();
            ctx.arc(W/2, H - 10, 80, Math.PI, Math.PI + pct * Math.PI, false);
            ctx.strokeStyle = pct > 0.75 ? '#5f8a5f' : pct > 0.4 ? '#d4a44a' : '#c0625a';
            ctx.stroke();

            // Text
            ctx.fillStyle = getComputedStyle(document.body)
                             .getPropertyValue('--md-default-fg-color') || '#333';
            ctx.font = 'bold 22px Inter, sans-serif';
            ctx.textAlign = 'center';
            ctx.fillText(Math.round(value), W/2, H - 20);
            ctx.font = '12px Inter, sans-serif';
            ctx.fillStyle = '#888';
            ctx.fillText(label, W/2, H - 4);
        }).call(this);
        """

    def scripts(self) -> list[str]:
        return []   # pure canvas — no external scripts


# Use in a page
app = ui.App("Gauge Demo")

@app.page("/")
def home(session):
    value = ui.state(65)

    with ui.Column(gap="var(--s-6)", align="center"):
        ui.Heading("Custom Gauge Component", level=1)
        GaugeChart(value, max_value=100, label="Score")
        ui.Slider(label="Adjust value", value=value, on_change=value.set, min=0, max=100)

app.run(port=8000)

Registering as an override

You can also register a CustomComponent subclass as an override for an existing built-in component:

@ui.override("Button")
class AnimatedButton(CustomComponent):
    def __init__(self, label, *, on_click=None, variant="primary", **kwargs):
        self._label    = label
        self._on_click = on_click
        self._variant  = variant

    def render(self) -> str:
        return (
            f'<button class="animated-btn animated-btn--{self._variant}">'
            f'{self._label}'
            f'</button>'
        )

    def client_script(self) -> str:
        return """
        this.querySelector('button').addEventListener('click', function() {
            this.classList.add('animate-click');
            setTimeout(() => this.classList.remove('animate-click'), 300);
        }.bind(this));
        """