Components

Wijjit ships with a wide range of input and display components. They are implemented under wijjit.elements and exposed to templates through wijjit.tags. This section summarizes what’s available and when to reach for each widget.

The examples referenced here are managed under examples/. We pull live snippets with literalinclude so the docs stay in sync with the runnable demos—open the referenced script if you want to try it yourself.

Form inputs

TextInput / TextArea

Single-line and multi-line editors (wijjit.elements.input.text). Support placeholder text, max length, width/height control, Enter actions, selection, copy/paste (via pyperclip), and cursor movement. Both emit ChangeEvent on edits and update state[id] automatically. Use bind=False to manage the value manually (useful for formatted or derived inputs) and attach action=... to submit on Enter. To read or write the text from Python, use the .value property — it is available uniformly on TextInput, TextArea, and CodeEditor (assigning element.value = "..." replaces the content and fires on_change).

examples/basic/simple_input_test.py – binding a textinput to state
@app.view("main", default=True)
def main_view():
    return render_template_string(
        """
{% vstack width=50 height=10 %}
  Status: {{ state.test }}
  {% textinput id="test" placeholder="Type here" width=30 %}{% endtextinput %}

  Press 'q' (or Ctrl+Q while typing) to quit
{% endvstack %}
        """
    )

textarea adds scrollbars, selection APIs, and clipboard shortcuts. It’s ideal for log editing, notes, or prompt composition. Pair it with derived state to show live counts, as demonstrated below.

By default Tab moves focus out of a textarea, as it does in a web form. Set capture_tab=True (with an optional tab_width, default 4) to make Tab insert an indent instead; Shift+Tab still moves focus backward, so the field can always be left. See When Tab belongs to the element instead.

textarea (and codeeditor, which extends it) supports undo with ``Ctrl+Z`` and redo with ``Ctrl+Y``. Typing a word is a single undo rather than one per letter, and a keypress that does several things at once - typing over a selection, or an insertion that triggers a hard-wrap reflow - undoes as one action. History is bounded at 200 edits and is discarded when the content is replaced programmatically (element.value = "..." with different text). undo() and redo() are also callable from Python.

Template excerpt from examples/widgets/textarea_demo.py
            """{% frame height="fill" title="TextArea demo" %}
{% textarea id="content" height=10 width=80 show_scrollbar=true border="single" %}{% endtextarea %}

  Lines: {{ lines }}  |  Chars: {{ chars }}  |  Wrap: {{ wrap_display }}
  [Shift+Arrows] Select  [Ctrl+C/X/V] Clipboard  [Ctrl+A] Select All  [Ctrl+Q] Quit
{% endframe %}
""",
CodeEditor

Syntax-highlighted code editor (wijjit.elements.input.code_editor). Extends TextArea with syntax highlighting powered by Pygments. Supports 500+ programming languages, multiple color themes (monokai, dracula, nord, github-light), line numbers, and automatic language detection.

Key attributes:

  • language - Programming language for highlighting (python, javascript, rust, etc.) or "auto" for detection

  • theme - Color theme (monokai, dracula, nord, github-light)

  • show_line_numbers - Display line numbers in the gutter (default: True)

  • filename_hint - Helps auto-detection when language="auto"

  • capture_tab - Tab inserts an indent rather than moving focus (default: True here, unlike TextArea); tab_width sets its size (default: 4). Shift+Tab still steps backward out of the editor - see When Tab belongs to the element instead.

Performance is optimized for large files through per-line token caching and debounced re-tokenization during edits. Inherits all TextArea features including selection, clipboard support, and scrolling.

examples/widgets/code_editor_demo.py - syntax highlighting with theme switching
@app.view("main", default=True)
def main_view():
    """Main view with code editor demo."""
    # Initialize state
    if "language" not in app.state:
        app.state["language"] = "python"
        app.state["theme"] = "monokai"
        app.state["editor"] = PYTHON_CODE

    return render_template_string(
        """
{% frame border="rounded" title="CodeEditor Demo - Syntax Highlighting" width=90 height=35 %}
  {% vstack spacing=1 %}
    {% hstack spacing=2 %}
      Language:
      {% button action="lang_python" %}Python{% endbutton %}
      {% button action="lang_js" %}JavaScript{% endbutton %}
      {% button action="lang_rust" %}Rust{% endbutton %}
    {% endhstack %}

    {% hstack spacing=2 %}
      Theme:
      {% button action="theme_monokai" %}Monokai{% endbutton %}
      {% button action="theme_dracula" %}Dracula{% endbutton %}
      {% button action="theme_nord" %}Nord{% endbutton %}
      {% button action="theme_github" %}GitHub{% endbutton %}
    {% endhstack %}

    {% codeeditor id="editor" language=state.language theme=state.theme
                  width=84 height=15 show_line_numbers=True tab_index=0 %}
    {% endcodeeditor %}

    {% hstack spacing=2 %}
      Language: {{ state.language }} | Theme: {{ state.theme }}
      | Press Ctrl+Q to quit
    {% endhstack %}
  {% endvstack %}
{% endframe %}
""",
        state=app.state,
    )
DataGrid

Spreadsheet-like data entry element (wijjit.elements.input.datagrid). Implements VisiCalc/Lotus 1-2-3 style editing with an entry line at the top for data input. Supports keyboard navigation (arrow keys, Tab, Enter), mouse interaction (click cells, scroll), and two-way state binding - committed edits are written back to state[id]. Note that DataGrid normalizes every cell to str, so the rows it writes back are strings.

Key attributes:

  • data - 2D list of cell values (list of rows)

  • columns - List of column headers (strings or dicts with key, label, width)

  • width / height - Grid dimensions in characters

  • show_row_numbers - Display row numbers on the left (default: True)

  • editable - Allow cell editing (default: True)

  • show_scrollbar - Show scrollbars when content exceeds viewport (default: True)

Navigation:

  • Arrow keys - Move between cells

  • Tab / Shift+Tab - Move to next/previous cell

  • Enter - Confirm edit and move down, or start editing current cell

  • Escape - Cancel current edit

  • Mouse click - Select cell, double-click to edit

  • Mouse scroll - Scroll through large datasets

Data formats (input and output):

  • List of lists - Default format: [["A", "B"], ["C", "D"]]

  • List of dicts - Each row as a dict: [{"name": "John", "age": 30}, ...]

  • pandas DataFrame - Automatically converts to/from DataFrames (pandas optional)

{% datagrid id="spreadsheet" width=60 height=15 %}
    columns:
      - {key: "name", label: "Name", width: 20}
      - {key: "price", label: "Price", width: 10}
      - {key: "qty", label: "Quantity", width: 10}
    data:
      - ["Widget", "9.99", "100"]
      - ["Gadget", "19.99", "50"]
{% enddatagrid %}

Working with DataGrid state:

# Get data as list of lists (default)
data = app.state["spreadsheet"]

# Get data as list of dicts
from wijjit.elements.input.datagrid import DataGrid
grid = app.get_element_by_id("spreadsheet")
data_as_dicts = grid.get_data_as_dicts()

# Get data as pandas DataFrame (if pandas available)
df = grid.get_data_as_dataframe()

# Set data from various formats
grid.set_data([["A", "B"], ["C", "D"]])  # list of lists
grid.set_data([{"col1": "A", "col2": "B"}])  # list of dicts
grid.set_data(pandas_df)  # DataFrame

See examples/widgets/datagrid_demo.py for a complete inventory management example with add/delete row functionality.

Button

Clickable action trigger (wijjit.elements.input.button). Attributes: action (the action id to emit), style (the bracket preset used to draw the button), and the universal class for theming. Style a “danger” button with a CSS class (class="danger") plus a matching theme entry rather than a variant attribute — there is none. Remember that action ids participate in handler routing—keep them short verbs (save, cancel) and reuse them across views to share behavior.

Checkbox / CheckboxGroup

Toggle booleans or sets of values. Groups accept options and maintain a list in state (state.selected_tags). Support tri-state rendering, keyboard navigation, and spacing options. Use them for preference panes or wizards.

examples/widgets/checkbox_demo.py – single inputs + grouped toggles
@app.view("main", default=True)
def main_view():
    """Main form view with checkboxes."""
    return render_template_string(
        """
{% frame title="Checkbox Demo" border="double" width=70 height=28 %}
  {% vstack spacing=1 padding=1 %}
    {% vstack spacing=0 %}
      {{ state.status }}
    {% endvstack %}

    {% vstack %}
      Individual Checkboxes:
      {% checkbox id="newsletter" label="Subscribe to newsletter" %}{% endcheckbox %}
      {% checkbox id="terms" label="I agree to the terms and conditions" %}{% endcheckbox %}
    {% endvstack %}

    {% vstack spacing=1 %}
      Email Preferences (Multiple Selection):
      {% checkbox id="notifications" label="Email notifications" %}{% endcheckbox %}
      {% checkbox id="updates" label="Product updates" %}{% endcheckbox %}
      {% checkbox id="marketing" label="Marketing emails" %}{% endcheckbox %}
    {% endvstack %}

    {% hstack spacing=2 %}
      {% button action="submit" %}Submit{% endbutton %}
      {% button action="reset" %}Reset{% endbutton %}
      {% button action="quit" %}Quit{% endbutton %}
    {% endhstack %}

    {% if state.submitted %}
      Submitted Values:
      Newsletter: {{ 'Yes' if state.newsletter else 'No' }}
      Terms Accepted: {{ 'Yes' if state.terms else 'No' }}
      Email Preferences:
      {% if state.notifications or state.updates or state.marketing %}
        {% if state.notifications %}- Email notifications{% endif %}
        {% if state.updates %}- Product updates{% endif %}
        {% if state.marketing %}- Marketing emails{% endif %}
      {% else %}
        None selected
      {% endif %}
    {% endif %}

    {% vstack spacing=0 %}
      Controls: [Tab/Shift+Tab] Navigate | [Space] Toggle | [q] Quit
    {% endvstack %}
  {% endvstack %}
{% endframe %}
        """
    )
Radio / RadioGroup

Mutually exclusive selection. Layout horizontally or vertically, optionally wrap inside a frame with legend="…". Combine with state.watch to react when users pick a new option.

Select

Drop-down picker with search/filter ability. Accepts dictionaries or tuples, can render icons next to labels, and integrates with dropdown overlays for large option sets.

Link

Inline clickable text element (wijjit.elements.display.link). Renders as styled text that can be focused and activated via keyboard (Enter/Space) or mouse click. Perfect for inline navigation, action triggers, or any clickable text that shouldn’t look like a button.

Key attributes:

  • action - Action name to trigger when clicked

  • class - CSS class for styling (e.g., text-danger, text-success)

{% hstack spacing=3 %}
  {% link action="home" %}Home{% endlink %}
  {% link action="settings" %}Settings{% endlink %}
  {% link action="logout" class="text-danger" %}Logout{% endlink %}
{% endhstack %}

See examples/widgets/contentview_demo.py for links used alongside HTML-formatted content.

Slider

Numeric input with draggable handle (wijjit.elements.input.slider). Supports both integer and float modes, keyboard navigation (Left/Right arrows, Home/End), and mouse interaction (click to set, drag to adjust).

Key attributes:

  • min - Minimum value (default: 0)

  • max - Maximum value (default: 100)

  • value - Initial value (defaults to min)

  • step - Increment for keyboard navigation (default: 1)

  • width - Track width in characters (default: 20)

  • float_mode - Return float instead of int (default: False)

  • label - Optional label before slider

  • show_value - Display current value after slider (default: True)

{# Integer slider #}
{% slider id="volume" min=0 max=100 value=50 %}{% endslider %}

{# Float slider with decimal step #}
{% slider id="opacity" min=0.0 max=1.0 step=0.1 float_mode=True label="Opacity" %}{% endslider %}

See examples/widgets/slider_demo.py for integer and float slider examples.

Toggle

Boolean switch with visual indicator (wijjit.elements.input.toggle). Provides clearer on/off feedback than a checkbox with colored block characters. Supports single label mode (label after switch) and dual label mode (labels on both sides).

Key attributes:

  • checked - Initial state (default: False)

  • label - Label text for single mode

  • label_mode - single (default) or dual

  • on_label - “On” text for dual mode (default: “ON”)

  • off_label - “Off” text for dual mode (default: “OFF”)

Colors are themeable via CSS: toggle.on, toggle.off, toggle:focus.

{# Single mode: switch + label #}
{% toggle id="dark_mode" label="Dark Mode" %}{% endtoggle %}

{# Dual mode: OFF [switch] ON #}
{% toggle id="theme" label_mode="dual" off_label="Light" on_label="Dark" %}{% endtoggle %}

See examples/widgets/toggle_demo.py for single and dual mode examples.

Special behaviors:

  • bind=False lets you manage the value manually (useful for derived or formatted inputs), and bind="some_key" binds to that state key instead of the id - so two widgets can share one key, and an id need not colonize one. See The bind attribute.

  • action on inputs triggers when Enter is pressed.

  • With bind=True the id is the state key (except for radio/radiogroup, which key off the group name). Wijjit auto-generates ids, but naming them yourself keeps focus/state debugging easier.

  • autofocus=True puts the cursor in this element when the app starts. Without it nothing is focused and the user must press Tab before typing does anything - see Initial focus: the autofocus attribute. Use it on one element per view.

Display widgets

ContentView

Unified content display element supporting multiple content types (wijjit.elements.display.contentview). Renders content in various formats with scrolling, borders, and keyboard/mouse navigation. This single component replaces the need for separate markdown, code, and HTML viewers.

Supported content types (content_type attribute):

  • "plain" / "text" - Plain text with word wrapping

  • "ansi" - Text with ANSI escape codes passed through

  • "html" - HTML-like markup with <b>, <i>, <u>, <s>, and <style> tags

  • "markdown" - Markdown with headings, lists, code blocks, and blockquotes

  • "rich" - Rich markup with [bold], [red], etc.

  • "code" - Syntax-highlighted code (500+ languages via Pygments)

Key attributes:

  • content_type - Content format (default: "plain")

  • width / height - Display dimensions

  • border - Border style: single, double, rounded, or none

  • title - Optional title in top border

  • show_scrollbar - Show vertical scrollbar (default: True)

Code-specific attributes:

  • language - Programming language for syntax highlighting (default: "python")

  • theme - Syntax highlighting theme (default: "monokai")

  • show_line_numbers - Display line numbers (default: False)

  • line_number_start - Starting line number (default: 1)

{# Markdown content #}
{% contentview id="docs" content_type="markdown" title="Documentation" height=15 %}
# Welcome

This is **bold** and *italic* text.

- Bullet points
- Multiple items
{% endcontentview %}

{# Syntax-highlighted code #}
{% contentview id="code" content_type="code" language="python" show_line_numbers=true title="Example" %}
def hello(name: str) -> str:
    return f"Hello, {name}!"
{% endcontentview %}

{# HTML-style formatting #}
{% contentview id="html" content_type="html" title="Styled Text" %}
<b>Bold</b> and <i>italic</i>
<style fg="red">Red text</style>
{% endcontentview %}

Keyboard controls: Up/Down arrows for line-by-line scrolling, PageUp/PageDown for page scrolling, Home/End to jump to start/end.

See examples/widgets/contentview_demo.py for a complete demonstration of all content types.

Table

Feature-rich table control with column sizing, alignment, zebra striping, sorting, and optional selection/highlighting. Works well for log viewers, data dashboards, and admin lists. Consider pairing with scrollable frames for large datasets or binding button actions to operate on selected rows. Assign table.data = rows to swap the contents reactively — it re-applies any active sort and re-clamps scroll, staying in sync (do not mutate the underlying list in place).

examples/widgets/table_demo.py – sorted table with action bar
@app.view("main", default=True)
def main_view():
    """Main view showcasing table element."""
    return render_template_string(
        """
{% frame title="Table Demo - User Directory" border="double" width=100 height=29 %}
  {% vstack spacing=1 padding=1 %}
    {% vstack spacing=0 %}
      {{ state.message }}
    {% endvstack %}

    {% vstack spacing=0 %}
      Instructions: Press TAB to focus table | Use Up/Down/PageUp/PageDown to scroll | Mouse wheel also works | Press 'q' to quit
    {% endvstack %}

    {% table id="users"
             data=state.users
             columns=["name", "email", "status", "age"]
             sortable=true
             width=94
             height=19
             show_header=true
             show_scrollbar=true
             border="single" %}
    {% endtable %}

    {% hstack spacing=2 %}
      {% button id="add_user_btn" action="add_user" %}Add User{% endbutton %}
      {% button id="clear_btn" action="clear_table" %}Clear Table{% endbutton %}
      {% button id="reset_btn" action="reset_table" %}Reset Table{% endbutton %}
      {% button id="quit_btn" action="quit" %}Quit{% endbutton %}
    {% endhstack %}
  {% endvstack %}
{% endframe %}
        """
    )  # noqa: E501
Tree

Hierarchical explorer for file systems, menus, or org charts. Nodes can be expanded/collapsed via keyboard or mouse. See examples/widgets/tree_demo.py for layout variations.

ListView

Scrollable vertical list that highlights the selected row. Great for menus, chat transcripts, or search results. Works nicely with app.on_action handlers that parse the row id (row_selected_<id> pattern).

LogView

Tail-like streaming buffer with manual/auto-scroll toggles. Supports severity coloring and timestamp columns. Pair it with background tasks to watch long-running jobs. Assign logview.lines = [...] (or the equivalent set_lines(...)) to replace the buffer reactively — it re-renders, re-clamps scroll, and honours auto-scroll. To append, reassign with the new list (logview.lines = logview.lines + [entry]).

StatusIndicator

Colored status indicator with extensible presets (wijjit.elements.display.status_indicator). Displays a colored circle (or other shape) to indicate status. Non-interactive display element perfect for dashboards and system status displays.

Built-in statuses: error (red), warning (yellow), success (green), info (blue), pending (cyan), active (bright green), inactive (dim), disabled (gray).

Key attributes:

  • status - Status name (default: info)

  • label - Optional label text after indicator

  • indicator_style - Shape: filled (default), hollow, square, ascii

  • custom_statuses - Dict to add/override status colors

{# Built-in statuses #}
{% status status="success" label="Connected" %}{% endstatus %}
{% status status="error" label="Failed" %}{% endstatus %}
{% status status="warning" label="Degraded" %}{% endstatus %}

{# Custom status with color #}
{% status status="processing" custom_statuses={"processing": "magenta"} label="Working..." %}{% endstatus %}

{# Different indicator styles #}
{% status status="info" indicator_style="hollow" label="Hollow" %}{% endstatus %}
{% status status="info" indicator_style="square" label="Square" %}{% endstatus %}

See examples/widgets/status_indicator_demo.py for a complete dashboard example.

ProgressBar / Spinner

Progress indicators for background tasks. progressbar accepts numeric value and optional label; spinner animates automatically if you set app.refresh_interval.

ProgressBar supports multiple display and visual styles:

  • Display styles (style attribute): filled (default), percentage, gradient, custom

  • Bar styles (bar_style attribute): Visual presets for the progress bar characters

    • block (default) - Solid Unicode block characters

    • thin - Thin horizontal line

    • thick - Heavy horizontal line

    • equals - Classic ==== style

    • arrow - Arrow/chevron >>>> style

    • dots - Bullet dot characters

    • ascii - Pure ASCII (# and -)

    • hash - Hash marks with spaces

    • pipe - Pipe characters

    • square - Square box characters

Example with different bar styles:

{% progressbar id="download" value=state.progress bar_style="equals" color="green" %}{% endprogressbar %}
{% progressbar id="upload" value=state.upload bar_style="arrow" style="gradient" %}{% endprogressbar %}
{% progressbar id="cpu" value=state.cpu bar_style="thin" %}{% endprogressbar %}

See examples/widgets/progress_demo.py for a complete demonstration of all bar styles.

StatusBar

Sticky footer for breadcrumbs, key hints, or status indicators. Combine with {% hstack %} sections to align regions left/center/right. examples/widgets/statusbar_demo.py shows how to surface view-scoped hints.

Notifications

Toast banners styled by severity (info, success, warning, error). There is no {% notification %} template tag — raise one from Python with app.notify("Saved!", severity="success"), which routes through wijjit.core.notification_manager.NotificationManager and auto-dismisses after duration seconds (pass duration=None to keep it up). Use them for asynchronous feedback or confirmations.

examples/widgets/notification_demo.py – framing actionable hints
@app.view("main", default=True)
def main_view():
    return render_template_string(
        """
{% vstack %}
  {% frame border="double" title="Notification System Demo" %}

    Welcome to the Notification Demo!

    Press keys to trigger different notifications:

    [1] Success notification
    [2] Error notification
    [3] Warning notification
    [4] Info notification
    [5] Notification with action button
    [6] Persistent notification (manual dismiss)
    [7] Notification with bell sound
    [8] Multiple notifications (stack test)
    [9] Clear all notifications

    Press Ctrl+Q to quit

    ---
    Active notifications: {{ state.active_count or 0 }}
    Total shown: {{ state.total_shown or 0 }}

  {% endframe %}
{% endvstack %}
        """
    )
ImageView

Renders raster images as ASCII/ANSI art in the terminal (wijjit.elements.display.image). Requires the images extra (pip install wijjit[images]), which pulls in Pillow. Exposed via the {% imageview %} tag (registered alias: Image). Accepts a file path, raw bytes, or a PIL Image as src and auto-binds it to state[id] unless bind=False.

Key attributes:

  • src - Image source: file path, bytes, or PIL Image

  • width / height - Size spec: int, "auto", "fill", or "50%" (default: "auto")

  • mode - Render mode: "color", "quadrant", or "braille" (default: "color")

  • threshold - Braille binarization cutoff: 0-255, or "auto" for Otsu’s method (default: "auto")

  • invert - Invert the threshold in braille mode (default: False)

  • background - Background RGB tuple for transparent pixels (default: (0, 0, 0))

The three modes trade colour against spatial detail:

Mode

Subpixels

Colour

Notes

"color"

1x2

full

Half-blocks (U+2580). The safe default.

"quadrant"

2x2

full

Quadrant blocks (U+2596-U+259F). Two colours per cell, full cell coverage. Usually the best choice for photographs.

"braille"

2x4

none

Braille dots (U+2800-U+28FF). Highest subpixel resolution, but monochrome, and the dots only partially cover each cell. Best for line art and high-contrast graphics.

Note

Denser full-coverage character sets exist - sextants (2x3, Unicode 13) and octants (2x4, Unicode 16) - but they are missing from common terminal fonts, including Cascadia Mono, the Windows Terminal default. Wijjit deliberately sticks to character sets that render everywhere.

{% imageview id="logo" src="assets/logo.png" width=40 %}{% endimageview %}
{% imageview src="photo.jpg" width=40 mode="quadrant" %}{% endimageview %}
{% imageview src="diagram.png" mode="braille" threshold=140 %}{% endimageview %}

Data Visualization

Wijjit includes a suite of data visualization components for dashboards, monitoring, and analytics. All chart elements support reactive updates - simply update the data and the chart re-renders automatically. These are provided by wijjit.elements.display and exposed via wijjit.tags.charts.

Sparkline

Compact inline trend visualization (wijjit.elements.display.sparkline). Perfect for embedding trends alongside text or in dense dashboards. Renders in a single row using braille characters for high resolution.

Key attributes:

  • data - List of numeric values

  • width - Display width in characters (default: 20)

  • style - Rendering style: line (braille), bar, or dot

  • show_current - Display the last value as text

  • show_minmax - Mark min/max points

{% sparkline data=cpu_history width=30 style="line" show_current=True %}{% endsparkline %}
BarChart

Horizontal bar chart with labels (wijjit.elements.display.barchart). Supports scrolling for large datasets, gradient or threshold-based coloring, and value display.

Key attributes:

  • data - List of values, tuples (label, value), or dicts {"label": ..., "value": ...}

  • width / height - Display dimensions

  • show_values - Display numeric values on bars

  • color_mode - Color mode: default, gradient, or threshold

  • max_value - Override automatic scaling

{% barchart data=sales_by_region width=40 height=8 show_values=True color_mode="gradient" %}{% endbarchart %}
ColumnChart

Vertical column chart with Y-axis (wijjit.elements.display.columnchart). Uses block characters for rendering with optional grid lines and axis labels.

Key attributes:

  • data - List of values or labeled data

  • width / height - Display dimensions

  • show_axis - Display Y-axis with tick marks

  • show_labels - Display X-axis labels

  • color_mode - Color mode for columns

{% columnchart data=monthly_revenue width=50 height=12 show_axis=True show_labels=True %}{% endcolumnchart %}
LineChart

High-resolution line chart using braille characters (wijjit.elements.display.linechart). Each character contains a 2x4 dot grid, enabling smooth curves in terminal displays. Supports multiple series, area fills, and axis display.

Key attributes:

  • data - List of values or list of series [{"values": [...], "label": ...}, ...]

  • width / height - Display dimensions

  • style - Rendering style: line, area, or dots

  • show_axis - Display axes with labels

  • show_legend - Display series legend (for multi-series)

{% linechart data=temperature_readings width=60 height=15 style="line" show_axis=True %}{% endlinechart %}
Gauge

Value indicator with linear or arc styles (wijjit.elements.display.gauge). Ideal for showing percentages, metrics, or bounded values with threshold coloring.

Key attributes:

  • value - Current value

  • min_value / max_value - Value range (default: 0-100)

  • style - Display style: linear (horizontal bar) or arc (semi-circular)

  • color_mode - Color mode: default, gradient, or threshold

  • label - Optional label text above gauge

  • unit - Unit suffix for value display (e.g., %, MB)

  • show_value - Display current value

  • show_minmax - Display min/max labels

{% gauge value=cpu_usage style="arc" label="CPU" unit="%" color_mode="threshold" %}{% endgauge %}
HeatMap

2D grid visualization with color intensity (wijjit.elements.display.heatmap). Uses block characters with RGB coloring to represent values. Great for correlation matrices, activity grids, or geographic data.

Key attributes:

  • data - 2D list of values [[row1...], [row2...], ...]

  • width / height - Display dimensions

  • color_scale - Color palette: viridis, plasma, inferno, cool, hot, greens, blues, reds

  • show_values - Display values in cells (space permitting)

  • row_labels / col_labels - Optional axis labels

{% heatmap data=correlation_matrix width=40 height=20 color_scale="viridis" %}{% endheatmap %}

All chart elements support reactive updates through state binding:

@app.on_action("refresh")
async def refresh_data(event):
    app.state.update({
        "cpu_history": await fetch_cpu_metrics(),
        "memory_usage": await get_memory_percent()
    })
    # Charts automatically re-render with new data

See examples/widgets/charts_demo.py for a complete interactive demonstration of all chart types.

Container widgets

TabbedPanel

Tabbed container for organizing content into switchable panels (wijjit.elements.display.tabbed_panel). Each tab displays a separate frame of content, with keyboard and mouse navigation between tabs. Ideal for settings panels, multi-page forms, or dashboards with distinct sections.

Key attributes:

  • tab_position - Position of tabs: top (default), bottom, left, right

  • width / height - Panel dimensions

  • border - Border style: single (default), double, rounded

  • active_tab_index - Initially active tab (default: 0)

Navigation:

  • Horizontal tabs (top/bottom): Left/Right arrows switch tabs

  • Vertical tabs (left/right): Up/Down arrows switch tabs

  • Mouse: Click on tab labels to switch

  • Content scrolling: Up/Down/PageUp/PageDown scroll the active tab’s content

State persistence: The active tab index is automatically saved to state[id] (where id is the element’s id attribute), preserving the user’s tab selection across re-renders.

{% tabbedpanel id="settings" tab_position="top" width=60 height=20 %}
    {% tab label="General" %}
        General settings content here...
        {% checkbox id="dark_mode" label="Dark Mode" %}{% endcheckbox %}
        {% checkbox id="notifications" label="Enable Notifications" %}{% endcheckbox %}
    {% endtab %}
    {% tab label="Advanced" %}
        Advanced settings content here...
        {% textinput id="api_key" placeholder="API Key" width=40 %}{% endtextinput %}
    {% endtab %}
    {% tab label="About" %}
        Application version 1.0.0
        Built with Wijjit
    {% endtab %}
{% endtabbedpanel %}

See examples/widgets/tabbedpanel_demo.py for a complete interactive demonstration with all tab positions.

Pager

Paged container that shows one {% page %} at a time with built-in navigation (wijjit.elements.display.pager). Useful for wizards, slideshows, onboarding flows, or any sequential multi-step UI. Each child {% page %} is a self-contained frame; the pager renders a “Page X of Y” indicator and Prev/Next controls.

Key attributes:

  • nav_position - Position of navigation controls: top, bottom (default), or both

  • show_indicator - Show the “Page X of Y” indicator (default: True)

  • show_titles - Show the page title in the indicator (default: False)

  • loop - Wrap from the last page back to the first (default: False)

  • current_page - State key name for page binding, or an initial page index (default: 0)

  • width / height - Pager dimensions (default: 60 x 20)

  • border - Border style: single (default), double, rounded, none

{% pager id="wizard" nav_position="bottom" show_indicator=True %}
    {% page title="Welcome" %}
        Welcome to the setup wizard.
    {% endpage %}
    {% page title="Account" %}
        {% textinput id="username" placeholder="Username" width=30 %}{% endtextinput %}
    {% endpage %}
    {% page title="Done" %}
        All set!
    {% endpage %}
{% endpager %}

Custom components

When the built-in set isn’t enough, implement a subclass of wijjit.elements.base.Element:

  1. Override render_to to paint into the wijjit.rendering.paint_context.PaintContext.

  2. Implement get_intrinsic_size if you need better sizing hints.

  3. Set focusable and implement handle_key / handle_mouse when appropriate.

  4. Expose the component via a Jinja extension (see wijjit/tags) or instantiate it inside a view and insert it into the layout tree manually.

Shipping an element as its own package

You do not have to fork Wijjit or monkeypatch it. wijjit.register_element() couples a VNode type name with your Element subclass and, optionally, a {% tag %} extension:

from wijjit import Element, register_element

class SparkGauge(Element):
    ...

register_element("SparkGauge", SparkGauge, tag="sparkgauge")

Because both the element registry and the Jinja environment are built per renderer, every new renderer drains this global registry at construction — so the running app and wijjit validate pick your element up automatically.

Call it at module level and declare a wijjit.plugins entry point (mirroring the pytest11 precedent) and an installed package registers itself on import:

[project.entry-points."wijjit.plugins"]
sparkgauge = "my_package.elements"

Wijjit.register_element is the instance-method form, which also live-patches an already-constructed app. Scope note: v1 covers leaf elements (self-closing or simple-body widgets); custom containers, which need layout-tree-builder and validator integration, are a deferred follow-up.

examples/advanced/plugin_element.py is a complete working example in about 30 lines.

Tips

  • Wrap inputs inside frames or stacks to control spacing and provide labels.

  • Use state to drive visual states through the universal class attribute (e.g., {% button class=state.in_progress and "busy" or "primary" %}), then style those classes in your theme.

  • For high-frequency components (logs, tables), prefer incremental updates to state rather than rebuilding entire datasets every tick.

  • Read the example gallery – most widgets live under examples/widgets/*_demo.py so you can run the exact code showcased here.

Next: learn how to orchestrate modals and overlays in Modal Dialogs & Overlays, then deep dive into interaction layers with Focus Navigation and Mouse Support.