Quick Start

This guide will get you building your first Wijjit application in just a few minutes.

Hello World

Let’s start with the simplest possible Wijjit app:

from wijjit import Wijjit, render_template_string

app = Wijjit()

@app.view("main", default=True)
def main_view():
    return render_template_string("Hello, World! Press 'q' to quit.")

@app.on_key("q")
def quit_app(event):
    app.quit()

if __name__ == "__main__":
    app.run()

Save this as hello.py and run it:

python hello.py

You should see “Hello, World!” displayed in your terminal. Press q to quit.

A template that is nothing but text needs no tags – Wijjit wraps it in a text element for you, so it renders exactly as if you had written {% text %}...{% endtext %}. Reach for the explicit tag when you want to control the line – align, wrap, or a class to style it; the next section wraps this one in a {% frame %} instead.

Understanding the Code

Let’s break down what’s happening:

  1. Import Wijjit: from wijjit import Wijjit imports the main app class

  2. Create app: app = Wijjit() creates a new Wijjit application

  3. Define view: @app.view("main", default=True) decorates a function that defines the main view

  4. Return template: The view function returns render_template_string(...) with the template source containing the UI

  5. Handle a key: @app.on_key("q") registers a handler that quits when q is pressed

  6. Run app: app.run() starts the event loop

Adding a Frame

Let’s add a border around our text using a frame:

from wijjit import Wijjit, render_template_string

app = Wijjit()

@app.view("main", default=True)
def main_view():
    return render_template_string("""
{% frame title="Welcome" border="rounded" width=50 height=10 %}
  Hello, World!

  This is a Wijjit TUI application.

  Press 'q' to quit.
{% endframe %}
        """)

@app.on_key("q")
def quit_app(event):
    app.quit()

if __name__ == "__main__":
    app.run()

Now the text is displayed inside a rounded box with a title!

Adding State and Input

Let’s create an interactive app with a text input and button:

from wijjit import Wijjit, render_template_string

app = Wijjit(initial_state={
    'name': '',
    'greeting': 'Please enter your name'
})

@app.view("main", default=True)
def main_view():
    return render_template_string("""
{% frame title="Greeting App" border="single" width=60 height=12 %}
  {% vstack spacing=1 padding=1 %}
    {{ state.greeting }}

    {% vstack spacing=0 %}
      Your name:
      {% textinput id="name" placeholder="Enter your name" width=30 autofocus=True %}{% endtextinput %}
    {% endvstack %}

    {% hstack spacing=2 %}
      {% button action="greet" %}Greet Me{% endbutton %}
      {% button action="quit" %}Quit{% endbutton %}
    {% endhstack %}
  {% endvstack %}
{% endframe %}
        """)

@app.on_action("greet")
def handle_greet(event):
    name = app.state.get('name', '').strip()
    if name:
        app.state['greeting'] = f"Hello, {name}! Nice to meet you!"
    else:
        app.state['greeting'] = "Please enter your name first"

@app.on_action("quit")
def handle_quit(event):
    app.quit()

if __name__ == '__main__':
    app.run()

Key Concepts Demonstrated

  1. State Management: initial_state sets up reactive state

  2. Templates: Jinja2 templates with custom tags (frame, vstack, textinput, button)

  3. State Binding: The textinput with id="name" automatically binds to app.state['name']

  4. Actions: Buttons trigger actions that are handled by @app.on_action() decorators

  5. Reactivity: Changing app.state['greeting'] automatically re-renders the UI

  6. Initial focus: autofocus=True puts the cursor in the name field on start. Without it an app begins with nothing focused and the user must press Tab before typing does anything - see Initial focus: the autofocus attribute

Login Form Example

Here’s a complete login form with validation:

from wijjit import Wijjit, render_template_string

app = Wijjit(initial_state={
    'username': '',
    'password': '',
    'status': 'Please enter your credentials',
})

@app.view("login", default=True)
def login_view():
    return render_template_string("""
{% frame title="Login" border="single" width=50 height=15 %}
  {% vstack spacing=1 padding=1 %}
    {{ state.status }}

    {% vstack spacing=0 %}
      Username:
      {% textinput id="username" placeholder="Enter username" width=30 autofocus=True %}{% endtextinput %}
    {% endvstack %}

    {% vstack spacing=0 %}
      Password:
      {% textinput id="password" placeholder="Enter password" width=30 action="login" %}{% endtextinput %}
    {% endvstack %}

    {% hstack spacing=2 %}
      {% button action="login" %}Login{% endbutton %}
      {% button action="clear" %}Clear{% endbutton %}
      {% button action="quit" %}Quit{% endbutton %}
    {% endhstack %}
  {% endvstack %}
{% endframe %}
        """)

@app.on_action("login")
def handle_login(event):
    username = app.state.get('username', '')
    password = app.state.get('password', '')

    if not username:
        app.state['status'] = 'Error: Username is required'
    elif not password:
        app.state['status'] = 'Error: Password is required'
    elif username == 'admin' and password == 'password':
        app.state['status'] = f'Success! Welcome, {username}!'
    else:
        app.state['status'] = 'Error: Invalid credentials'
        app.state['password'] = ''  # Clear password on failed login

@app.on_action("clear")
def handle_clear(event):
    app.state['username'] = ''
    app.state['password'] = ''
    app.state['status'] = 'Form cleared'

@app.on_action("quit")
def handle_quit(event):
    app.quit()

if __name__ == '__main__':
    app.run()

Try logging in with: * Username: admin * Password: password

Features Shown

  • Form validation

  • Error messages

  • Multiple buttons with different actions

  • Clearing form data

  • Initial focus via autofocus=True on the username field, then Tab/Shift+Tab to move between fields

  • Enter key in password field triggers login action

Common Patterns

Keyboard Shortcuts

Add custom keyboard shortcuts:

@app.on_key("ctrl+s")
def save(event):
    # Save logic here
    pass

@app.on_key("ctrl+q")
def quit_app(event):
    app.quit()

Working with Lists

Display and interact with lists of data:

app = Wijjit(initial_state={
    'items': ['Apple', 'Banana', 'Cherry'],
    'selected': None
})

@app.view("main", default=True)
def main_view():
    items_text = '\n'.join(f"- {item}" for item in app.state['items'])
    return render_template_string("""
{% frame title="Items" %}
  {{ items_text }}
{% endframe %}
        """, items_text=items_text)

Next Steps

Now that you’ve built your first Wijjit apps, you can:

Where to Go From Here

  • Tutorial: Tutorial: Persistent Todo App - Build a complete todo list app

  • User Guide: Core Concepts - Deep dive into Wijjit concepts

  • Examples: 74 runnable scripts across examples/basic, examples/widgets, examples/advanced, examples/styling, and examples/apps

  • API Reference: Core API - Detailed API documentation