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
| 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¶
render()produces an HTML string. FluxUI wraps it in a<div data-flux-id="...">and inserts it into the DOM.- After insertion, FluxUI executes
client_script()withthisbound to the root element. - 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));
"""