Inline Rendering

Wijjit’s inline rendering lets you output styled UI directly to terminal scrollback without entering full-screen (alternate buffer) mode. This is ideal for:

  • CLI tools that output formatted results

  • Progress indicators that update in-place

  • Quick interactive prompts that don’t need a full TUI

  • Styled output that should remain visible after the program exits

Two APIs are provided:

  • render_inline() - One-shot rendering of a template to stdout

  • InlineApp - Interactive inline display with reactive state updates

One-shot Rendering

Use render_inline() when you want to output styled content once:

from wijjit import render_inline

# Render a styled frame with data
render_inline('''
{% frame title="Build Results" border="rounded" %}
  {% vstack %}
    Status: {{ status }}
    Duration: {{ duration }}s
    Tests: {{ passed }}/{{ total }} passed
  {% endvstack %}
{% endframe %}
''',
    status="SUCCESS",
    duration=12.5,
    passed=42,
    total=42
)

The output appears in your terminal’s scrollback and remains visible after the script exits.

Options

render_inline() accepts these parameters:

  • template - Jinja2 template string with Wijjit tags

  • width - Output width in columns (default: terminal width)

  • height - Output height in lines (default: “auto” to fit content)

  • print_output - If True (default), print to stdout. If False, return the ANSI string.

  • file - File object to write to (default: sys.stdout)

  • template_dir - Directory for template file loading (used by {% include %} and friends; default: None)

  • **context - Template context variables

# Capture output instead of printing
ansi_str = render_inline(template, print_output=False, **data)

# Fixed width rendering
render_inline(template, width=60, **data)

# Write to a file
with open("output.txt", "w") as f:
    render_inline(template, file=f, **data)

Interactive Inline Apps

Use InlineApp when you need reactive updates that refresh in-place:

import asyncio
from wijjit import InlineApp

template = '''
{% frame title="Download Progress" %}
  {% vstack %}
    {% progressbar value=state.progress %}{% endprogressbar %}
    {{ state.status }}
  {% endvstack %}
{% endframe %}
'''

async def main():
    async with InlineApp(template, initial_state={"progress": 0, "status": "Starting..."}) as app:
        for i in range(101):
            app.state.progress = i
            app.state.status = f"Downloading... {i}%"
            await asyncio.sleep(0.05)
        app.state.status = "Complete!"
        await asyncio.sleep(1)

asyncio.run(main())

The display updates in-place as state changes. When the context manager exits, the content remains in scrollback.

InlineApp Parameters

  • template - Jinja2 template string

  • height - Display height: integer for fixed, "auto" to calculate from content

  • width - Display width (default: terminal width)

  • initial_state - Dictionary of initial state values

  • refresh_interval - Seconds between refresh checks (default: 0.1)

  • enable_input - Enable keyboard input handling (default: False)

  • quit_key - Key to exit when input enabled (default: “ctrl+q”)

Keyboard Input

Enable enable_input=True to accept keyboard input:

template = '''
{% frame title="Quick Entry" %}
  {% vstack %}
    Name:
    {% textinput id="name" placeholder="Enter your name" %}{% endtextinput %}

    Email:
    {% textinput id="email" placeholder="Enter your email" %}{% endtextinput %}

    Press Ctrl+Q when done
  {% endvstack %}
{% endframe %}
'''

async with InlineApp(template, enable_input=True, quit_key="ctrl+q") as app:
    await app.wait()  # Block until quit key pressed

print(f"Name: {app.state.name}")
print(f"Email: {app.state.email}")

Features when input is enabled:

  • Tab / Shift+Tab - Navigate between focusable elements

  • Typing - Input goes to the focused element. Nothing is focused until the first Tab unless an element declares autofocus=True - mark the first field to let users type straight away (see Initial focus: the autofocus attribute)

  • Quit key - Exit the app (default: Ctrl+Q)

  • State sync - Element values automatically sync to app.state by element ID

Methods

  • app.state - Reactive state object (changes trigger re-render)

  • app.refresh() - Force immediate re-render

  • app.stop() - Stop the app programmatically

  • await app.wait() - Block until app is stopped

Animation Support

InlineApp automatically animates spinners:

template = '''
{% frame title="Processing" %}
  {% hstack %}
    {% spinner active=true %}{% endspinner %}
    {{ state.message }}
  {% endhstack %}
{% endframe %}
'''

async with InlineApp(template, initial_state={"message": "Working..."}) as app:
    await asyncio.sleep(3)
    app.state.message = "Done!"

The spinner animates automatically via the internal refresh loop.

Comparison with Full-Screen Apps

Feature

Wijjit (full)

InlineApp

Alternate screen buffer

Yes

No

Content after exit

Cleared

Stays in scrollback

Mouse support

Yes

No

Keyboard input

Yes

Optional

Multiple views

Yes

Single template

Overlays/modals

Yes

No

Use case

Full TUI apps

CLI output, prompts

Examples

See these examples in the examples/basic/ directory:

  • inline_demo.py - One-shot rendering with frames, tables, nested layouts

  • inline_progress_demo.py - Interactive progress bar with InlineApp

  • inline_input_demo.py - Interactive forms with keyboard input