Skip to content

UI components

Presentational, shadcn-styled, server-rendered builders. Each returns an HTML string and runs its arguments through Golit's renderer, so components nest with DataFrames, charts, and each other. See the UI components tutorial.

Presentational components — golit.ui.

Unlike widgets (reactive inputs), these are pure builders a view returns: they take renderables and produce shadcn-styled, server-rendered HTML that slots into the fragment pipeline. Any argument is run through :func:render_value, so you can nest a DataFrame, a chart figure, another component, or trusted HTML uniformly::

import golit.ui as ui

@app.view
def panel(by_region, total):
    return ui.card(
        ui.columns([ui.metric("Revenue", f"${total:,}", delta="+8%"), chart_fig]),
        title="Overview",
    )

Layout (card/columns/grid/tabs/expander/accordion/divider), display (metric/scorecard/alert/badge/progress/skeleton/spinner), and rich data (table/markdown/code/json_view/heading/caption).

card

card(
    *body: Any,
    title: str | None = None,
    subtitle: str | None = None,
    footer: Any = None,
) -> str

A surface card with optional title/subtitle/footer.

Source code in python/golit/ui.py
def card(
    *body: Any,
    title: str | None = None,
    subtitle: str | None = None,
    footer: Any = None,
) -> str:
    """A surface card with optional title/subtitle/footer."""
    head = ""
    if title or subtitle:
        head = '<div class="mb-4">'
        if title:
            head += f'<h3 class="font-headline text-lg font-bold tracking-tight">{esc(title)}</h3>'
        if subtitle:
            head += f'<p class="text-sm text-on-surface-variant mt-0.5">{esc(subtitle)}</p>'
        head += "</div>"
    foot = ""
    if footer is not None:
        foot = (
            '<div class="mt-4 pt-4 border-t border-outline-variant/20 text-sm '
            f'text-on-surface-variant">{render_value(footer)}</div>'
        )
    return (
        '<div class="golit-card bg-surface-container-low rounded-xl p-6 shadow-sm">'
        f"{head}{_join(body)}{foot}</div>"
    )

columns

columns(
    items: list[Any],
    *,
    gap: int = 6,
    widths: list[int] | None = None,
) -> str

Lay renderables out in a responsive row. widths (summing to 12) gives a custom split; otherwise the columns are equal and stack on small screens.

Source code in python/golit/ui.py
def columns(items: list[Any], *, gap: int = 6, widths: list[int] | None = None) -> str:
    """Lay renderables out in a responsive row. ``widths`` (summing to 12) gives a
    custom split; otherwise the columns are equal and stack on small screens."""
    if widths is not None:
        cells = "".join(
            f'<div class="col-span-12 md:col-span-{w}">{render_value(it)}</div>'
            for it, w in zip(items, widths, strict=True)
        )
        return f'<div class="grid grid-cols-12 gap-{gap}">{cells}</div>'
    n = max(1, len(items))
    cells = "".join(f"<div>{render_value(it)}</div>" for it in items)
    return f'<div class="grid grid-cols-1 md:grid-cols-{n} gap-{gap}">{cells}</div>'

grid

grid(
    items: list[Any], *, cols: int = 3, gap: int = 6
) -> str

A fixed-column responsive grid (1 → 2 → cols across breakpoints).

Source code in python/golit/ui.py
def grid(items: list[Any], *, cols: int = 3, gap: int = 6) -> str:
    """A fixed-column responsive grid (1 → 2 → ``cols`` across breakpoints)."""
    cells = "".join(f"<div>{render_value(it)}</div>" for it in items)
    return (
        f'<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-{cols} gap-{gap}">{cells}</div>'
    )

tabs

tabs(
    panels: dict[str, Any], *, default: str | None = None
) -> str

A client-side tab group (Alpine). panels maps label → renderable.

Source code in python/golit/ui.py
def tabs(panels: dict[str, Any], *, default: str | None = None) -> str:
    """A client-side tab group (Alpine). ``panels`` maps label → renderable."""
    labels = list(panels)
    start = labels.index(default) if default in labels else 0
    active = "text-primary border-primary"
    inactive = "text-on-surface-variant border-transparent hover:text-on-surface"
    bar = "".join(
        f'<button type="button" x-on:click="tab={i}" '
        f""":class="tab==={i} ? '{active}' : '{inactive}'" """
        'class="px-4 py-2 text-sm font-semibold border-b-2 transition-colors">'
        f"{esc(label)}</button>"
        for i, label in enumerate(labels)
    )
    bodies = "".join(
        f'<div x-show="tab==={i}" x-cloak>{render_value(content)}</div>'
        for i, content in enumerate(panels.values())
    )
    return (
        f'<div class="golit-tabs" x-data="{{ tab: {start} }}">'
        f'<div class="flex gap-1 border-b border-outline-variant/30 mb-4">{bar}</div>'
        f"<div>{bodies}</div></div>"
    )

expander

expander(title: str, *body: Any, open: bool = False) -> str

A collapsible section (native <details> — no JS needed).

Source code in python/golit/ui.py
def expander(title: str, *body: Any, open: bool = False) -> str:
    """A collapsible section (native ``<details>`` — no JS needed)."""
    op = " open" if open else ""
    return (
        f'<details class="golit-expander bg-surface-container-low rounded-xl px-5"{op}>'
        '<summary class="flex items-center justify-between cursor-pointer py-4 font-semibold '
        f'text-sm">{esc(title)}'
        '<span class="material-symbols-outlined golit-chev text-on-surface-variant">'
        "expand_more</span></summary>"
        f'<div class="pb-4 text-sm">{_join(body)}</div></details>'
    )

accordion

accordion(sections: dict[str, Any]) -> str

A stack of independently collapsible sections.

Source code in python/golit/ui.py
def accordion(sections: dict[str, Any]) -> str:
    """A stack of independently collapsible sections."""
    return (
        '<div class="golit-accordion flex flex-col gap-2">'
        + "".join(expander(title, body) for title, body in sections.items())
        + "</div>"
    )

divider

divider(*, label: str | None = None) -> str

A horizontal rule, optionally with a centered label.

Source code in python/golit/ui.py
def divider(*, label: str | None = None) -> str:
    """A horizontal rule, optionally with a centered label."""
    if label:
        return (
            '<div class="flex items-center gap-3 my-6">'
            '<div class="h-px bg-outline-variant/30 flex-1"></div>'
            '<span class="text-xs uppercase tracking-widest text-on-surface-variant">'
            f"{esc(label)}</span>"
            '<div class="h-px bg-outline-variant/30 flex-1"></div></div>'
        )
    return '<hr class="border-0 h-px bg-outline-variant/30 my-6">'

metric

metric(
    label: str,
    value: Any,
    *,
    delta: Any = None,
    delta_color: str = "normal",
    help: str | None = None,
) -> str

A KPI: big value, label, and an optional up/down delta. delta_color is normal (up=good/green), inverse (down=good), or off (neutral).

Source code in python/golit/ui.py
def metric(
    label: str,
    value: Any,
    *,
    delta: Any = None,
    delta_color: str = "normal",
    help: str | None = None,
) -> str:
    """A KPI: big value, label, and an optional up/down delta. ``delta_color`` is
    ``normal`` (up=good/green), ``inverse`` (down=good), or ``off`` (neutral)."""
    help_html = ""
    if help:
        help_html = (
            '<span class="material-symbols-outlined text-sm text-outline cursor-help" '
            f'title="{esc(help)}">help</span>'
        )
    return (
        '<div class="golit-metric">'
        '<div class="flex items-center gap-1 text-xs font-bold uppercase tracking-widest '
        f'text-on-surface-variant">{esc(label)}{help_html}</div>'
        '<div class="flex items-baseline gap-2 mt-1">'
        f'<span class="font-headline text-3xl font-bold tracking-tight">{esc(value)}</span>'
        f"{_delta_html(delta, delta_color)}</div></div>"
    )

scorecard

scorecard(
    label: str,
    value: Any,
    *,
    delta: Any = None,
    delta_color: str = "normal",
    icon: str | None = None,
    caption: str | None = None,
    kind: str = "default",
) -> str

A standalone KPI card: a label, a big value, an optional trend delta, an optional Material Symbols icon, and an optional caption footer — where :func:metric is a bare KPI for composing, scorecard is the complete surface you drop into a :func:grid for a dashboard header row::

ui.grid([
    ui.scorecard("Revenue", "$84.2k", delta="+8%", icon="payments", kind="primary"),
    ui.scorecard("Churn", "2.1%", delta="-0.4%", delta_color="inverse",
                 icon="trending_down", caption="vs last month"),
], cols=2)

delta_color behaves as in :func:metric (normal / inverse / off). kind tints the icon — default / primary / success / warning / error.

Source code in python/golit/ui.py
def scorecard(
    label: str,
    value: Any,
    *,
    delta: Any = None,
    delta_color: str = "normal",
    icon: str | None = None,
    caption: str | None = None,
    kind: str = "default",
) -> str:
    """A standalone KPI **card**: a label, a big value, an optional trend ``delta``, an
    optional Material Symbols ``icon``, and an optional ``caption`` footer — where
    :func:`metric` is a bare KPI for composing, ``scorecard`` is the complete surface you
    drop into a :func:`grid` for a dashboard header row::

        ui.grid([
            ui.scorecard("Revenue", "$84.2k", delta="+8%", icon="payments", kind="primary"),
            ui.scorecard("Churn", "2.1%", delta="-0.4%", delta_color="inverse",
                         icon="trending_down", caption="vs last month"),
        ], cols=2)

    ``delta_color`` behaves as in :func:`metric` (``normal`` / ``inverse`` / ``off``).
    ``kind`` tints the icon — ``default`` / ``primary`` / ``success`` / ``warning`` /
    ``error``."""
    bg, fg = _SCORECARD_ACCENT.get(kind, _SCORECARD_ACCENT["default"])
    icon_html = ""
    if icon:
        icon_html = (
            f'<div class="flex items-center justify-center w-10 h-10 rounded-full {bg} {fg}">'
            f'<span class="material-symbols-outlined text-xl">{esc(icon)}</span></div>'
        )
    caption_html = ""
    if caption:
        caption_html = f'<p class="text-xs text-on-surface-variant mt-2">{esc(caption)}</p>'
    return (
        '<div class="golit-scorecard bg-surface-container-low rounded-xl p-5 shadow-sm">'
        '<div class="flex items-start justify-between gap-3">'
        '<span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant">'
        f"{esc(label)}</span>{icon_html}</div>"
        '<div class="flex items-baseline gap-2 mt-2">'
        f'<span class="font-headline text-3xl font-bold tracking-tight">{esc(value)}</span>'
        f"{_delta_html(delta, delta_color)}</div>{caption_html}</div>"
    )

alert

alert(
    *body: Any, kind: str = "info", title: str | None = None
) -> str

A callout. kind is info/success/warning/error.

Source code in python/golit/ui.py
def alert(*body: Any, kind: str = "info", title: str | None = None) -> str:
    """A callout. ``kind`` is info/success/warning/error."""
    cls, icon = _ALERT.get(kind, _ALERT["info"])
    title_html = f'<p class="font-semibold mb-0.5">{esc(title)}</p>' if title else ""
    return (
        f'<div class="golit-alert flex gap-3 border rounded-lg px-4 py-3 {cls}">'
        f'<span class="material-symbols-outlined text-xl">{icon}</span>'
        f'<div class="text-sm">{title_html}{_join(body)}</div></div>'
    )

badge

badge(text: Any, *, kind: str = 'default') -> str

A small status pill.

Source code in python/golit/ui.py
def badge(text: Any, *, kind: str = "default") -> str:
    """A small status pill."""
    cls = _BADGE.get(kind, _BADGE["default"])
    return (
        '<span class="golit-badge inline-flex items-center px-2.5 py-0.5 rounded-full '
        f'text-xs font-semibold {cls}">{esc(text)}</span>'
    )

progress

progress(
    value: float,
    *,
    label: str | None = None,
    total: float = 1.0,
) -> str

A progress bar. value is out of total (default a 0–1 fraction).

Source code in python/golit/ui.py
def progress(value: float, *, label: str | None = None, total: float = 1.0) -> str:
    """A progress bar. ``value`` is out of ``total`` (default a 0–1 fraction)."""
    frac = 0.0 if total == 0 else value / total
    pct = round(min(1.0, max(0.0, frac)) * 100, 1)
    head = ""
    if label:
        head = (
            '<div class="flex justify-between text-xs text-on-surface-variant mb-1">'
            f"<span>{esc(label)}</span><span>{pct:g}%</span></div>"
        )
    return (
        f'<div class="golit-progress">{head}'
        '<div class="w-full h-2 bg-surface-container-highest rounded-full overflow-hidden">'
        f'<div class="h-full bg-primary-container rounded-full transition-all" '
        f'style="width: {pct:g}%"></div></div></div>'
    )

skeleton

skeleton(*, lines: int = 3) -> str

A loading placeholder of pulsing bars.

Source code in python/golit/ui.py
def skeleton(*, lines: int = 3) -> str:
    """A loading placeholder of pulsing bars."""
    widths = [100, 92, 78, 96, 64]
    bars = "".join(
        '<div class="h-4 bg-surface-container-highest rounded animate-pulse" '
        f'style="width: {widths[i % len(widths)]}%"></div>'
        for i in range(max(1, lines))
    )
    return f'<div class="golit-skeleton flex flex-col gap-3">{bars}</div>'

spinner

spinner(*, label: str | None = None) -> str

An indeterminate spinner with an optional label.

Source code in python/golit/ui.py
def spinner(*, label: str | None = None) -> str:
    """An indeterminate spinner with an optional label."""
    lbl = f'<span class="text-sm text-on-surface-variant">{esc(label)}</span>' if label else ""
    return (
        '<div class="golit-spinner flex items-center gap-3">'
        '<div class="w-5 h-5 border-2 border-primary-container border-t-transparent '
        f'rounded-full animate-spin"></div>{lbl}</div>'
    )

table

table(
    df: Any,
    *,
    max_rows: int = 50,
    highlight: str | None = None,
    paginate: bool = False,
    per_page: int = 10,
    page_sizes: tuple[int, ...] = (10, 25, 50, 100),
) -> str

A styled table from a Polars DataFrame; highlight emphasizes a column.

With paginate=True the rows (up to max_rows, materialised into the page) are paged entirely client-side via Alpine — a per-page selector plus prev/next controls, with no server round-trip. Raise max_rows to page through more rows; page_sizes are the per-page choices. Non-DataFrame values fall back to the default renderer.

Source code in python/golit/ui.py
def table(
    df: Any,
    *,
    max_rows: int = 50,
    highlight: str | None = None,
    paginate: bool = False,
    per_page: int = 10,
    page_sizes: tuple[int, ...] = (10, 25, 50, 100),
) -> str:
    """A styled table from a Polars ``DataFrame``; ``highlight`` emphasizes a column.

    With ``paginate=True`` the rows (up to ``max_rows``, materialised into the page)
    are paged entirely client-side via Alpine — a per-page selector plus prev/next
    controls, with no server round-trip. Raise ``max_rows`` to page through more rows;
    ``page_sizes`` are the per-page choices. Non-DataFrame values fall back to the
    default renderer."""
    import polars as pl

    if not isinstance(df, pl.DataFrame):
        return render_value(df)
    head = df.head(max_rows)
    cols = "".join(
        f'<th class="px-4 py-3 text-left{" text-primary" if c == highlight else ""}">'
        f"{esc(c)}</th>"
        for c in head.columns
    )

    def _cells(row: tuple) -> str:
        return "".join(
            f'<td class="px-4 py-2.5 font-mono text-xs'
            f'{" text-primary font-semibold" if head.columns[i] == highlight else ""}">'
            f"{esc(v)}</td>"
            for i, v in enumerate(row)
        )

    if paginate:
        return _paginated_table(head, df.height, cols, _cells, per_page, page_sizes)

    rows = "".join(
        '<tr class="hover:bg-surface-container transition-all">' + _cells(row) + "</tr>"
        for row in head.iter_rows()
    )
    more = ""
    if df.height > max_rows:
        more = (
            '<p class="text-[10px] text-on-surface-variant text-right pt-2 font-mono uppercase '
            f'tracking-widest">showing {max_rows} of {df.height} rows</p>'
        )
    return (
        '<div class="golit-table-wrap overflow-x-auto">'
        '<table class="golit-table w-full text-left border-collapse">'
        '<thead><tr class="bg-surface-container-high/50 font-mono text-[10px] uppercase '
        f'tracking-widest text-outline">{cols}</tr></thead>'
        f'<tbody class="divide-y divide-outline-variant/10 text-sm">{rows}</tbody>'
        f"</table>{more}</div>"
    )

code

code(src: str, *, lang: str | None = None) -> str

A monospaced code block with an optional language tag.

Source code in python/golit/ui.py
def code(src: str, *, lang: str | None = None) -> str:
    """A monospaced code block with an optional language tag."""
    tag = ""
    if lang:
        tag = (
            '<span class="absolute top-2 right-3 text-[10px] uppercase tracking-widest '
            f'text-outline font-mono">{esc(lang)}</span>'
        )
    return (
        f'<div class="golit-code relative">{tag}'
        '<pre class="bg-surface-container-highest rounded-lg p-4 overflow-x-auto text-xs '
        f'font-mono text-on-surface leading-relaxed"><code>{esc(src)}</code></pre></div>'
    )

json_view

json_view(obj: Any, *, indent: int = 2) -> str

Pretty-print a JSON-serializable object into a code block.

Source code in python/golit/ui.py
def json_view(obj: Any, *, indent: int = 2) -> str:
    """Pretty-print a JSON-serializable object into a code block."""
    try:
        rendered = json.dumps(obj, indent=indent, default=str)
    except (TypeError, ValueError):
        rendered = str(obj)
    return code(rendered, lang="json")

heading

heading(text: str, *, level: int = 2) -> str

A section heading (levels 1–6).

Source code in python/golit/ui.py
def heading(text: str, *, level: int = 2) -> str:
    """A section heading (levels 1–6)."""
    level = min(6, max(1, level))
    size = {1: "text-3xl", 2: "text-2xl", 3: "text-xl", 4: "text-lg"}.get(level, "text-base")
    return (
        f'<h{level} class="golit-heading font-headline {size} font-bold tracking-tight">'
        f"{esc(text)}</h{level}>"
    )

caption

caption(text: str) -> str

Small, muted helper text.

Source code in python/golit/ui.py
def caption(text: str) -> str:
    """Small, muted helper text."""
    return f'<p class="golit-caption text-xs text-on-surface-variant">{esc(text)}</p>'

gt_theme

gt_theme(gt: Any) -> Any

Style a great_tables GT to match golit's shadcn tables, and return it.

Maps golit's Material-3 light surface onto Great Tables' tab_options — clean white body, a tinted uppercase column-label row, hairline row rules instead of stripes, no outer borders — so a returned GT sits in the page like a native :func:table. Apply it around your built table::

return ui.gt_theme(GT(df).tab_header("Sales").fmt_currency("revenue"))

Your own tab_options / formatting stay; only the theme chrome is set. Takes (and returns) a GT object — duck-typed, so golit needs no great_tables dependency to offer it.

Source code in python/golit/ui.py
def gt_theme(gt: Any) -> Any:
    """Style a `great_tables` ``GT`` to match golit's shadcn tables, and return it.

    Maps golit's Material-3 light surface onto Great Tables' ``tab_options`` — clean white body,
    a tinted uppercase column-label row, hairline row rules instead of stripes, no outer borders
    — so a returned ``GT`` sits in the page like a native :func:`table`. Apply it around your
    built table::

        return ui.gt_theme(GT(df).tab_header("Sales").fmt_currency("revenue"))

    Your own ``tab_options`` / formatting stay; only the theme chrome is set. Takes (and returns)
    a ``GT`` object — duck-typed, so golit needs no ``great_tables`` dependency to offer it."""
    return gt.tab_options(
        table_font_names=_GT_FONT,
        table_font_size="14px",
        table_width="100%",  # fill the card like a native golit table()
        table_background_color="#ffffff",  # surface-container-lowest
        table_font_color="#191c1e",  # on-surface
        table_font_color_light="#424752",  # on-surface-variant
        table_border_top_style="none",
        table_border_bottom_style="none",
        table_border_left_style="none",
        table_border_right_style="none",
        heading_background_color="#f2f4f6",  # surface-container-low
        heading_title_font_weight="700",
        heading_title_font_size="18px",
        heading_subtitle_font_size="13px",
        heading_border_bottom_style="none",
        column_labels_background_color="#e6e8ea",  # surface-container-high
        column_labels_font_weight="700",
        column_labels_font_size="11px",
        column_labels_text_transform="uppercase",
        column_labels_border_top_style="none",
        column_labels_border_bottom_width="1px",
        column_labels_border_bottom_color="#c2c6d4",  # outline-variant
        data_row_padding="8px",
        row_striping_include_table_body=False,
        table_body_hlines_style="solid",
        table_body_hlines_width="1px",
        table_body_hlines_color="#c2c6d4",  # outline-variant
        table_body_vlines_style="none",
        table_body_border_top_style="none",
        table_body_border_bottom_style="solid",
        table_body_border_bottom_width="1px",
        table_body_border_bottom_color="#c2c6d4",
        source_notes_background_color="#f2f4f6",
        source_notes_font_size="11px",
        stub_background_color="#f2f4f6",
        stub_border_style="none",
    )

chat

chat(
    channel: str,
    *,
    author: str = "You",
    title: str | None = None,
    placeholder: str = "Message…",
    height: int = 384,
) -> str

A live chat panel backed by a WebSocket at /ws/<channel>.

Renders a message log that fills as messages arrive (over HTMX's ws extension) and an input that sends over the socket. author is the sender's display name; height is the log height in pixels. By default every message relays to all clients on the channel; register an @app.on_message(channel) handler to add bot/assistant/moderation behavior.

Source code in python/golit/ui.py
def chat(
    channel: str,
    *,
    author: str = "You",
    title: str | None = None,
    placeholder: str = "Message…",
    height: int = 384,
) -> str:
    """A live chat panel backed by a WebSocket at ``/ws/<channel>``.

    Renders a message log that fills as messages arrive (over HTMX's ``ws``
    extension) and an input that sends over the socket. ``author`` is the sender's
    display name; ``height`` is the log height in pixels. By default every message
    relays to all clients on the channel; register an ``@app.on_message(channel)``
    handler to add bot/assistant/moderation behavior."""
    log_id = f"golit-chat-{esc(channel)}-log"
    head = (
        f'<div class="px-1 pb-3 font-headline text-lg font-bold tracking-tight">{esc(title)}</div>'
        if title
        else ""
    )
    return (
        '<div class="golit-chat flex flex-col bg-surface-container-low rounded-xl p-4" '
        f'hx-ext="ws" ws-connect="/ws/{esc(channel)}">{head}'
        f'<div id="{log_id}" class="golit-chat-log flex flex-col gap-2 overflow-y-auto pr-1" '
        f'style="height: {int(height)}px"></div>'
        '<form ws-send autocomplete="off" x-data x-on:submit="setTimeout(() => $el.reset(), 0)" '
        'class="golit-chat-form flex gap-2 mt-3">'
        f'<input type="hidden" name="author" value="{esc(author)}">'
        f'<input name="message" placeholder="{esc(placeholder)}" autocomplete="off" '
        'class="flex-1 bg-surface-container-highest border-none rounded-lg px-3 py-2 text-sm '
        'font-body text-on-surface focus:ring-2 focus:ring-primary">'
        '<button type="submit" class="bg-primary text-on-primary rounded-lg px-4 py-2 text-sm '
        'font-semibold hover:opacity-90 transition-all">Send</button>'
        "</form></div>"
    )

webcam

webcam(
    name: str,
    *,
    title: str | None = None,
    height: int = 384,
    width: int | None = None,
) -> str

A live video panel showing the server-side stream registered as name with @app.stream(name) — for webcam / computer-vision views.

Frames arrive as an MJPEG (multipart/x-mixed-replace) response that the browser plays natively in a plain <img>: no client JS, and the connection is independent of the reactive graph, so the view never re-renders mid-stream. Pair it with a producer that reads a camera and draws its detector's output on each frame::

@app.stream("camera")
def camera():
    ...  # yield annotated JPEG bytes or RGB arrays

@app.view
def live() -> str:
    return ui.webcam("camera", title="Detections")

height is the display height in pixels; width (optional) caps the width, else it scales to its container. The frame keeps its aspect ratio (letterboxed, not cropped).

Source code in python/golit/ui.py
def webcam(
    name: str,
    *,
    title: str | None = None,
    height: int = 384,
    width: int | None = None,
) -> str:
    """A live video panel showing the server-side stream registered as ``name`` with
    ``@app.stream(name)`` — for webcam / computer-vision views.

    Frames arrive as an MJPEG (``multipart/x-mixed-replace``) response that the browser
    plays natively in a plain ``<img>``: no client JS, and the connection is independent of
    the reactive graph, so the view never re-renders mid-stream. Pair it with a producer
    that reads a camera and draws its detector's output on each frame::

        @app.stream("camera")
        def camera():
            ...  # yield annotated JPEG bytes or RGB arrays

        @app.view
        def live() -> str:
            return ui.webcam("camera", title="Detections")

    ``height`` is the display height in pixels; ``width`` (optional) caps the width, else it
    scales to its container. The frame keeps its aspect ratio (letterboxed, not cropped)."""
    head = (
        f'<div class="px-1 pb-3 font-headline text-lg font-bold tracking-tight">{esc(title)}</div>'
        if title
        else ""
    )
    style = f"height: {int(height)}px"
    if width is not None:
        style += f"; max-width: {int(width)}px"
    return (
        '<div class="golit-webcam flex flex-col bg-surface-container-low rounded-xl p-4">'
        f"{head}"
        '<div class="golit-webcam-frame overflow-hidden rounded-lg bg-black" '
        f'style="{style}">'
        f'<img src="/golit/stream/{esc(name)}" alt="{esc(title or name)}" '
        'class="golit-webcam-img w-full h-full object-contain"></div></div>'
    )

camera

camera(
    name: str,
    *,
    title: str | None = None,
    height: int = 384,
    width: int = 640,
    fps: int = 12,
    quality: float = 0.6,
) -> str

A live panel that streams the visitor's own webcam up to the server, runs the processor registered with @app.on_frame(name) on each frame, and shows the result.

The browser grabs the camera with getUserMedia, sends JPEG frames over a WebSocket (/golit/camera/<name>), and paints each annotated frame the server sends back — keeping one frame in flight, so a slow processor just lowers the rate. Use this when the camera is on the client; for a feed produced on the server use :func:webcam instead. Example::

@app.on_frame("detector")
def detect(frame):
    ...                       # return an annotated RGB array or JPEG bytes

@app.view
def live() -> str:
    return ui.camera("detector", title="Your camera")

width caps the captured/sent frame width in pixels (smaller = faster); height is the display height; fps is the target capture rate; quality is the JPEG quality (0–1) of the uploaded frames. Needs a secure contexthttps or localhost — for camera access; on insecure origins it shows a notice instead.

Source code in python/golit/ui.py
def camera(
    name: str,
    *,
    title: str | None = None,
    height: int = 384,
    width: int = 640,
    fps: int = 12,
    quality: float = 0.6,
) -> str:
    """A live panel that streams the **visitor's own webcam** up to the server, runs the
    processor registered with ``@app.on_frame(name)`` on each frame, and shows the result.

    The browser grabs the camera with ``getUserMedia``, sends JPEG frames over a WebSocket
    (``/golit/camera/<name>``), and paints each annotated frame the server sends back — keeping
    one frame in flight, so a slow processor just lowers the rate. Use this when the camera is on
    the *client*; for a feed produced on the server use :func:`webcam` instead. Example::

        @app.on_frame("detector")
        def detect(frame):
            ...                       # return an annotated RGB array or JPEG bytes

        @app.view
        def live() -> str:
            return ui.camera("detector", title="Your camera")

    ``width`` caps the captured/sent frame width in pixels (smaller = faster); ``height`` is the
    display height; ``fps`` is the target capture rate; ``quality`` is the JPEG quality (0–1) of
    the uploaded frames. Needs a **secure context** — ``https`` or ``localhost`` — for camera
    access; on insecure origins it shows a notice instead."""
    head = (
        f'<div class="px-1 pb-3 font-headline text-lg font-bold tracking-tight">{esc(title)}</div>'
        if title
        else ""
    )
    return (
        '<div class="golit-camera flex flex-col bg-surface-container-low rounded-xl p-4" '
        f'data-golit-camera="{esc(name)}" data-width="{int(width)}" data-fps="{int(fps)}" '
        f'data-quality="{float(quality):g}">'
        f"{head}"
        '<div class="golit-camera-frame relative overflow-hidden rounded-lg bg-black" '
        f'style="height: {int(height)}px">'
        # off-screen (not display:none, so frames keep decoding) capture source
        '<video class="golit-camera-src" autoplay playsinline muted '
        'style="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none"></video>'
        f'<img alt="{esc(title or name)}" '
        'class="golit-camera-out w-full h-full object-contain">'
        '<div class="golit-camera-status absolute inset-0 flex items-center justify-center '
        'text-center text-sm text-on-surface-variant px-4">Starting camera…</div>'
        "</div></div>"
    )

recorder

recorder(
    name: str,
    *,
    title: str | None = None,
    max_seconds: int = 30,
    hint: str | None = None,
    playback: bool = True,
    download: bool = True,
) -> str

A microphone recorder: the visitor records a clip, it's uploaded to the server as 16-bit PCM WAV, and the @app.on_audio(name) handler's result is shown back here.

Click to record, click to stop (or it auto-stops at max_seconds). The clip travels over a WebSocket (/golit/audio/<name>); whatever the handler returns is rendered into the panel — a transcript, an analysis, any :mod:golit.ui component — or, if it returns audio bytes, played back in an inline player. The mic mirror of :func:camera. Example::

@app.on_audio("note")
def handle(wav: bytes):
    ...                       # decode (stdlib `wave`), transcribe/analyze
    return ui.alert("Got it", kind="success")

@app.view
def live() -> str:
    return ui.recorder("note", title="Voice note")

max_seconds caps the recording length; hint is an optional caption under the button. With playback (default) the clip you just recorded is loaded into an inline player so you can hear it; with download (default) a link saves it as recording.wav. If the handler returns audio bytes, the player switches to that. Needs a secure contexthttps or localhost — for mic access; otherwise it shows a notice.

Source code in python/golit/ui.py
def recorder(
    name: str,
    *,
    title: str | None = None,
    max_seconds: int = 30,
    hint: str | None = None,
    playback: bool = True,
    download: bool = True,
) -> str:
    """A microphone **recorder**: the visitor records a clip, it's uploaded to the server as
    16-bit PCM WAV, and the ``@app.on_audio(name)`` handler's result is shown back here.

    Click to record, click to stop (or it auto-stops at ``max_seconds``). The clip travels over
    a WebSocket (``/golit/audio/<name>``); whatever the handler returns is rendered into the
    panel — a transcript, an analysis, any :mod:`golit.ui` component — or, if it returns audio
    ``bytes``, played back in an inline player. The mic mirror of :func:`camera`. Example::

        @app.on_audio("note")
        def handle(wav: bytes):
            ...                       # decode (stdlib `wave`), transcribe/analyze
            return ui.alert("Got it", kind="success")

        @app.view
        def live() -> str:
            return ui.recorder("note", title="Voice note")

    ``max_seconds`` caps the recording length; ``hint`` is an optional caption under the button.
    With ``playback`` (default) the clip you just recorded is loaded into an inline player so you
    can hear it; with ``download`` (default) a link saves it as ``recording.wav``. If the handler
    returns audio ``bytes``, the player switches to that. Needs a **secure context** — ``https``
    or ``localhost`` — for mic access; otherwise it shows a notice."""
    head = (
        f'<div class="px-1 font-headline text-lg font-bold tracking-tight">{esc(title)}</div>'
        if title
        else ""
    )
    hint_html = (
        f'<p class="golit-recorder-hint text-xs text-on-surface-variant">{esc(hint)}</p>'
        if hint
        else ""
    )
    download_html = (
        '<a class="golit-recorder-download hidden inline-flex items-center gap-1 text-sm '
        'text-primary hover:underline w-fit" download="recording.wav">'
        '<span class="material-symbols-outlined text-base">download</span>Download</a>'
        if download
        else ""
    )
    return (
        '<div class="golit-recorder flex flex-col gap-3 bg-surface-container-low rounded-xl p-4" '
        f'data-golit-recorder="{esc(name)}" data-max-seconds="{int(max_seconds)}" '
        f'data-playback="{1 if playback else 0}">'
        f"{head}"
        '<div class="flex items-center gap-3">'
        '<button type="button" class="golit-recorder-btn inline-flex items-center gap-2 '
        'bg-primary text-on-primary rounded-lg px-4 py-2 text-sm font-semibold '
        'hover:opacity-90 transition-all">'
        '<span class="material-symbols-outlined text-xl golit-recorder-icon">mic</span>'
        '<span class="golit-recorder-label">Record</span></button>'
        '<span class="golit-recorder-time font-mono text-sm text-on-surface-variant">0:00</span>'
        '<span class="golit-recorder-status text-sm text-on-surface-variant"></span>'
        "</div>"
        f"{hint_html}"
        '<audio class="golit-recorder-audio w-full hidden" controls></audio>'
        f"{download_html}"
        '<div class="golit-recorder-out text-sm"></div>'
        "</div>"
    )

markdown

markdown(src: str) -> str

Render a common Markdown subset (headings, emphasis, lists, blockquote, fenced code, links, rules) to styled HTML — no external dependency.

Source code in python/golit/ui.py
def markdown(src: str) -> str:
    """Render a common Markdown subset (headings, emphasis, lists, blockquote,
    fenced code, links, rules) to styled HTML — no external dependency."""
    lines = src.split("\n")
    parts: list[str] = []
    para: list[str] = []
    items: list[str] = []
    list_tag: str | None = None

    def flush_para() -> None:
        if para:
            parts.append(f'<p class="mb-3 leading-relaxed">{_md_inline(" ".join(para))}</p>')
            para.clear()

    def flush_list() -> None:
        nonlocal list_tag
        if items:
            cls = "list-decimal" if list_tag == "ol" else "list-disc"
            body = "".join(f"<li>{_md_inline(t)}</li>" for t in items)
            parts.append(f'<{list_tag} class="{cls} ml-6 mb-3 space-y-1">{body}</{list_tag}>')
            items.clear()
            list_tag = None

    i = 0
    while i < len(lines):
        stripped = lines[i].strip()
        if stripped.startswith("```"):
            flush_para()
            flush_list()
            lang = stripped[3:].strip() or None
            buf = []
            i += 1
            while i < len(lines) and not lines[i].strip().startswith("```"):
                buf.append(lines[i])
                i += 1
            parts.append(code("\n".join(buf), lang=lang))
            i += 1
            continue
        if not stripped:
            flush_para()
            flush_list()
        elif re.match(r"^#{1,6}\s", stripped):
            flush_para()
            flush_list()
            level = len(stripped) - len(stripped.lstrip("#"))
            size = {1: "text-2xl", 2: "text-xl", 3: "text-lg"}.get(level, "text-base")
            content = _md_inline(stripped[level:].strip())
            parts.append(
                f'<h{level} class="font-headline {size} font-bold tracking-tight mt-4 mb-2">'
                f"{content}</h{level}>"
            )
        elif stripped in ("---", "***", "___"):
            flush_para()
            flush_list()
            parts.append(divider())
        elif stripped.startswith("> "):
            flush_para()
            flush_list()
            parts.append(
                '<blockquote class="border-l-4 border-outline-variant pl-4 italic '
                f'text-on-surface-variant mb-3">{_md_inline(stripped[2:])}</blockquote>'
            )
        elif re.match(r"^[-*]\s+", stripped):
            flush_para()
            if list_tag not in (None, "ul"):
                flush_list()
            list_tag = "ul"
            items.append(re.sub(r"^[-*]\s+", "", stripped))
        elif re.match(r"^\d+\.\s+", stripped):
            flush_para()
            if list_tag not in (None, "ol"):
                flush_list()
            list_tag = "ol"
            items.append(re.sub(r"^\d+\.\s+", "", stripped))
        else:
            flush_list()
            para.append(stripped)
        i += 1

    flush_para()
    flush_list()
    return f'<div class="golit-markdown text-sm">{"".join(parts)}</div>'