Textual Serve
textual-serve is an open-source project that allows you to serve and access your Textual TUI (Text-based User Interface) applications via a web browser. The Textual app runs on a machine/server under your control and communicates with the browser via a custom protocol over WebSockets. End-users interacting with the app via their browser do not have direct access to the machine the application is running on, only to the running Textual app. It effectively turns Textual TUIs into multi-user web applications. The current version is 1.1.3, and it has a regular release cadence with recent updates.
Warnings
- gotcha Pasting text into Textual inputs via Ctrl+V (or equivalent) in a web browser served by `textual-serve` may not work as expected if the text was copied from outside the GUI. This is a known issue.
- breaking `textual-serve` relies heavily on the `textual` framework. `textual` underwent significant changes and breaking API updates frequently in its pre-1.0 versions. While `textual-serve` itself is past 1.0, ensure compatibility with the `textual` version you are using, as updates to `textual` might indirectly affect `textual-serve` applications.
- gotcha The `Server` class expects a shell command that successfully launches a Textual application. If the command is incorrect or the Textual app fails to start, `textual-serve` will encounter a `RuntimeError` or similar issues.
- gotcha It's crucial to understand that `textual-serve` does not expose a raw shell in the browser. Instead, it communicates with the running Textual application via a custom WebSocket protocol. This is a security feature, but it means users cannot execute arbitrary commands on the host system.
Install
-
pip install textual-serve
Imports
- Server
from textual_serve.server import Server
Quickstart
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Static
from textual_serve.server import Server
class MinimalApp(App):
BINDINGS = [("q", "quit", "Quit")]
def compose(self) -> ComposeResult:
yield Header()
yield Static("Hello from Textual Served!")
yield Footer()
def action_quit(self) -> None:
self.exit()
# Save the above Textual app as 'my_app.py'
# Then run the textual-serve server programmatically:
# Note: This will block and serve the app on http://localhost:8000
# You can also use the command line: textual serve my_app.py
if __name__ == "__main__":
# To run a Textual app directly:
# app = MinimalApp()
# app.run()
# To serve it via textual-serve
print("Starting textual-serve on http://localhost:8000...")
print("Press Ctrl+C to stop the server.")
server = Server("python my_app.py", host="localhost", port=8000)
server.serve()