Views & rendering¶
A view (@app.view) is a renderable leaf. Whatever it returns, Golit turns into an HTML fragment and swaps into a <section> keyed by the view's id. Because the transport is just HTML fragments, a view can return many different things — and Golit picks how to render based on the type of the value.
What a view can return¶
Golit resolves a return value in this order — first match wins:
- An object with a
__golit_render__()method (theRendererprotocol) → its returned markup. - A
str→ used verbatim as trusted, developer-authored markup. bytes→ decoded as UTF-8.- A Lets-Plot spec → rendered to static SVG.
- A Plotly / Altair / Bokeh figure → an interactive client-side chart mount.
- Anything with
to_svg()→ wrapped as SVG. - A Polars
DataFrame→ a styled HTML table. - A DuckDB relation → materialized to Polars, then a table.
- A Great Tables
GTobject → its self-contained HTML. - Anything with
_repr_html_()(e.g. pandas) → that HTML. - A Matplotlib figure → SVG via
savefig. - Anything else → its
repr(), escaped, in a<pre>.
None renders as empty.
Strings are trusted HTML
A str you return is inserted without escaping — it's meant for markup you wrote. Never interpolate untrusted user input into a returned string without escaping it yourself. For escaping a value, golit.widgets.esc (or Python's html.escape) is right there. For data, return a DataFrame or use golit.ui components, which escape for you.
Returning HTML¶
The simplest view returns a string:
@app.view
def kpi(filtered: pl.DataFrame) -> str:
total = int(filtered["revenue"].sum()) if filtered.height else 0
return f"<h3 class='text-3xl font-bold'>${total:,}</h3>"
Golit's page shell loads Tailwind (with the Material-3 token palette) and the golit.ui styles, so utility classes like text-3xl, bg-surface-container-low, and text-primary are available in your markup. You don't have to use them — any HTML works.
Returning a DataFrame¶
Return a Polars frame and get a styled, paginated table for free:
The default table shows up to 50 rows with a "showing 50 of N" footer. For more control (row cap, column highlight), use golit.ui.table.
Returning a Great Tables table¶
For a polished display table — formatted currency, spanners, source notes, embedded bars — build a Great Tables GT object from your Polars (or pandas) frame and return it. Golit detects it and embeds its self-contained HTML — the styles are scoped to the table and there's no JavaScript, so it just works inside the page, and it re-renders reactively like any view:
from great_tables import GT
import golit.ui as ui
@app.view
def report(filtered: pl.DataFrame):
return ui.gt_theme( # match golit's shadcn tables
GT(filtered, rowname_col="Region")
.tab_header(title="Regional Sales")
.fmt_currency(columns="Revenue", decimals=0)
.fmt_percent(columns="Growth", decimals=1)
)
ui.gt_theme restyles a GT to match golit's surface — clean body, a tinted uppercase column-label row, hairline rules — so it sits in the page like a native ui.table instead of Great Tables' default white card. It's optional: return a bare GT and it still renders, just with GT's own styling. Needs the extra: pip install "golit[tables]". See examples/great_tables.
Returning a chart¶
Return a Lets-Plot spec, or a Plotly/Altair/Bokeh figure — Golit detects each and renders appropriately. That's the whole next chapter: Charts.
Returning a component¶
The golit.ui builders return strings of styled HTML, so you can return one directly, or compose several:
import golit.ui as ui
@app.view
def panel(by_region, total: int) -> str:
return ui.card(
ui.metric("Revenue", f"${total:,}", delta="+8%"),
ui.table(by_region, highlight="revenue"),
title="Overview",
)
A view re-renders only when its inputs change¶
A view is a node like any other. It re-renders when something it depends on produces a new value — and is skipped otherwise. The classic illustration, from the sales_explorer example:
@app.view
def overview(data: pl.DataFrame) -> str:
# Depends only on `data` — unaffected by the slider/region inputs.
return f"<p>{data.height} rows · ${int(data['revenue'].sum()):,} total</p>"
Moving a slider that feeds a different view leaves overview exactly as it was — no recompute, no swap, nothing on the wire.
Next¶
Charts — static SVG and interactive figures, from the same Polars frame.