Skip to content

UI components

golit.ui is a library of presentational components — shadcn-styled, server-rendered HTML. Unlike widgets (which are reactive inputs), these are pure builders a view returns. Each takes renderables and produces a styled HTML string.

import golit.ui as ui

They compose through the renderer

Every component argument is run through Golit's render_value, so you can nest anything uniformly — a DataFrame, a chart figure, another component, or trusted HTML:

@app.view
def panel(by_region: pl.DataFrame, total: int) -> str:
    return ui.card(
        ui.columns([
            ui.metric("Revenue", f"${total:,}", delta="+8%"),
            ui.table(by_region, highlight="revenue"),
        ]),
        title="Overview",
    )

That uniformity is the point: a card doesn't care whether you hand it a string, a frame, or another card.

Layout & containers

Component Purpose
card(*body, title=None, subtitle=None, footer=None) A surface card with optional header/footer.
columns(items, *, gap=6, widths=None) A responsive row; widths (summing to 12) gives a custom split.
grid(items, *, cols=3, gap=6) A fixed-column responsive grid (1 → 2 → cols).
tabs(panels, *, default=None) A client-side tab group (Alpine); panels maps label → renderable.
expander(title, *body, open=False) A collapsible section (native <details>).
accordion(sections) A stack of independently collapsible sections.
divider(*, label=None) A horizontal rule, optionally labeled.

Display & status

Component Purpose
metric(label, value, *, delta=None, delta_color="normal", help=None) A bare KPI: big value, label, optional up/down delta.
scorecard(label, value, *, delta=None, delta_color="normal", icon=None, caption=None, kind="default") A standalone KPI card — icon, value, trend, caption. Drop several in a grid for a header row.
alert(*body, kind="info", title=None) A callout — info / success / warning / error.
badge(text, *, kind="default") A small status pill.
progress(value, *, label=None, total=1.0) A progress bar; value is out of total.
skeleton(*, lines=3) A loading placeholder of pulsing bars.
spinner(*, label=None) An indeterminate spinner.

metric's delta_color: "normal" (up = good/green), "inverse" (down = good), or "off" (neutral). A leading - in the delta marks it down.

Rich data

Component Purpose
table(df, *, max_rows=50, highlight=None, paginate=False, per_page=10, page_sizes=(10, 25, 50, 100)) A styled table from a Polars frame; highlight emphasizes a column. paginate=True adds client-side paging with a per-page selector.
gt_theme(gt) Restyle a Great Tables GT to match golit's surface; return the themed GT from a view. See Views.
markdown(src) A common-Markdown-subset renderer (headings, emphasis, lists, blockquote, fenced code, links, rules) — no external dependency.
code(src, *, lang=None) A monospaced code block with an optional language tag.
json_view(obj, *, indent=2) Pretty-printed JSON in a code block.
heading(text, *, level=2) A section heading (levels 1–6).
caption(text) Small, muted helper text.

Paginating a table

By default table shows the first max_rows and footnotes the rest. Pass paginate=True for a table the user can page through, with a per-page selector — all client-side, no extra reactive wiring:

@app.view
def rows(filtered: pl.DataFrame) -> str:
    return ui.table(filtered, paginate=True, per_page=25, max_rows=400)

The rows up to max_rows are sent to the browser and paged with Alpine; raise max_rows to page through more (it caps how much data ships, and the footer notes when the frame was larger). page_sizes sets the choices in the per-page selector. Because a view re-renders whenever its inputs change, filtering naturally resets the table to page 1.

Realtime

Component Purpose
chat(channel, *, author="You", title=None, …) A live, WebSocket-backed chat panel.
webcam(name, *, title=None, height=384, width=None) A live video panel showing a server-side MJPEG stream.
camera(name, *, title=None, height=384, width=640, fps=12, quality=0.6) The visitor's own webcam, processed server-side and painted back.
recorder(name, *, title=None, max_seconds=30, hint=None, playback=True, download=True) A microphone recorder; the clip uploads as WAV and the @app.on_audio result is shown, with inline playback + a download link for your clip.

These are different from the others: rather than rendering once, they hold a live connection and update on their own. chat opens a bidirectional WebSocket and appends messages as they arrive — see WebSocket chat. webcam shows a server-side frame producer (@app.stream(name)) as native MJPEG; camera streams the visitor's webcam up to an @app.on_frame(name) handler and displays the result — both under Video streams. recorder captures the mic and hands each clip to an @app.on_audio(name) handler — see Audio recording.

A worked example

import golit.ui as ui


@app.view
def status(filtered: pl.DataFrame) -> str:
    if not filtered.height:
        return ui.alert("No rows match the current filter.", kind="warning", title="Empty")
    return ui.alert(
        ui.badge(f"{filtered.height} rows", kind="primary") + " match the filter.",
        kind="success",
        title="Live",
    )


@app.view
def detail(filtered: pl.DataFrame, compact: bool = switch("Compact", default=False)) -> str:
    shown = filtered.select("region", "revenue") if compact else filtered
    summary = ui.markdown(f"### Summary\n\n- **{filtered.height}** rows after filtering\n")
    return ui.card(
        ui.tabs({"Table": ui.table(shown, highlight="revenue"), "Summary": summary}),
        title="Detail",
    )

The components_gallery example wires these into a full reactive dashboard.

Escaping

golit.ui components escape the values you pass through them, so it's safe to feed user data into metric, badge, table, and friends. The exception is a raw str you build yourself and return from a view — that's trusted markup.

Next

Arrange your views on the page: Page layout.