Skip to content

Overview

Widgets are dashboard-sized cards on a panel home page. Each widget is a Python class (or fluent instance) that renders a self-contained block of UI — a KPI strip, a chart, an embedded table, or custom HTML. Operators land on the dashboard for a glanceable summary; widgets are how you fill that glance without building a bespoke page for every metric.

Register widget classes on the panel with .widgets([...]). The default Dashboard collects them, checks who can see each one, sorts by .sort(...), and lays them out in a responsive grid by column span. Prefer many small widgets over one kitchen-sink card so visibility and layout stay independent.

app/providers/orbit_panel_provider.py
from almasix.orbit import Panel
from almasix.orbit.widgets import StatsOverviewWidget, ChartWidget, TableWidget
Panel.make("admin")
.path("admin")
.widgets([StatsOverviewWidget, ChartWidget, TableWidget])

Orbit Widgets overview (light)

Orbit Widgets overview (dark)

Each widget renders a <section class="or-widget"> with an optional header and body. Interactive charts boot through Alpine (orbitChart / orbitSparkline) with Chart.js or ApexCharts loaded by the panel shell.

Widget Role
Stats overview Row of fluent Stat cards (optional sparklines)
Charts Line / bar / doughnut charts via Chart.js or ApexCharts
Tables Embed a configured Table on the dashboard
Custom Widget Override render_body for bespoke HTML
app/orbit/widgets/examples.py
from almasix.orbit.widgets import (
ChartWidget,
Stat,
StatsOverviewWidget,
TableWidget,
Widget,
)
from almasix.orbit.tables import Table, TextColumn
StatsOverviewWidget.make("overview").stats([
Stat.make("Users").value(1280).color("success").icon("heroicon-o-users"),
Stat.make("Posts").value(342).color("primary"),
])
ChartWidget.make("signups")
.heading("Signups")
.chart_type("bar")
.labels(["Mon", "Tue", "Wed"])
.datasets([{"label": "Users", "data": [3, 7, 4]}])
TableWidget.make("recent").table(
Table.make().columns([TextColumn.make("title")]).records(recent_posts)
)

.heading(...) and .description(...) set the widget chrome above the body. Class attributes heading / description work the same via __init_subclass__.

app/orbit/widgets/heading.py
from almasix.orbit.widgets import Widget
Widget.make("notes")
.heading("Release notes")
.description("Latest shipping updates for the team.")

Orbit Widget heading (light)

Orbit Widget heading (dark)

Widgets are sorted ascending by .sort(...) (or class attribute sort) before render. Lower numbers appear first.

app/orbit/widgets/sort.py
from almasix.orbit.widgets import StatsOverviewWidget, ChartWidget
class OverviewStats(StatsOverviewWidget):
sort = 1
class SignupsChart(ChartWidget):
sort = 10

Orbit Widget sort (light)

Orbit Widget sort (dark)

.column_span(2), .column_span("full"), or .column_span_full() control grid placement. Pass a breakpoint map for responsive spans:

app/orbit/widgets/span.py
from almasix.orbit.widgets import ChartWidget
ChartWidget.make("wide")
.heading("Traffic")
.column_span("full")
ChartWidget.make("responsive")
.heading("By device")
.column_span({"md": 1, "lg": 2})

Orbit Widget column span (light)

Orbit Widget column span (dark)

.polling_interval(15) (or "15s") emits data-polling for the host to refresh the widget on an interval.

app/orbit/widgets/polling.py
from almasix.orbit.widgets import StatsOverviewWidget
StatsOverviewWidget.make("live")
.heading("Live metrics")
.polling_interval(15)

.lazy() marks the widget with data-lazy="true" and a placeholder so the shell can defer heavy bodies until visible.

app/orbit/widgets/lazy.py
from almasix.orbit.widgets import ChartWidget
ChartWidget.make("deferred")
.heading("Heavy chart")
.lazy()

Override classmethod can_view(**ctx) or chain .can_view_when(...) on an instance. Unauthorized widgets are omitted from the grid.

app/orbit/widgets/visibility.py
from almasix.orbit.widgets import Widget
class AdminOnly(Widget):
@classmethod
def can_view(cls, **ctx) -> bool:
user = ctx.get("user")
return bool(user and getattr(user, "is_admin", False))
Widget.make("beta").can_view_when(lambda **ctx: ctx.get("feature_beta") is True)

Orbit Widget visibility (light)

Orbit Widget visibility (dark)

Subclass Widget and implement render_body. Use page_filters / filter_value when the dashboard passes filter state.

app/orbit/widgets/welcome_widget.py
from almasix.orbit.widgets import Widget
from almasix.orbit.support.html import e
class WelcomeWidget(Widget):
sort = 0
heading = "Welcome"
def render_body(self, state=None, **ctx) -> str:
brand = e(str(ctx.get("brand") or "Orbit"))
return f'<p class="or-muted">Hello from {brand}.</p>'

Orbit custom widget (light)

Orbit custom widget (dark)

app/providers/orbit_panel_provider.py
Panel.make("admin")
.widgets([OverviewStats, SignupsChart, RecentPosts, WelcomeWidget])

The default dashboard calls panel.get_widgets(). Override Dashboard.get_widgets for a page-specific set, or pass widgets=[...] into Dashboard.render for tests and custom pages. See Dashboard.