Viaduc is probably the simplest way to create a nice-looking gui using python and a tiny bit of html/css/js. No server and everything in one file.
See the code for this example here.
viaduc uses pywebview and Bootstrap to provide a gui for other tools and scripts.
$ pip install viaduc
To install a development version run
pip install git+https://github.com/dtmilano/viaduc
The simplest Viaduc program instantiates a Viaduc object, like this (simplest.py)
#! /usr/bin/env python3
from viaduc import Viaduc
if __name__ == '__main__':
Viaduc()
and you will see this window
Then, let's do something more interesting implementing the Presentation class.
Let's add a title and some html which includes some metatags ({{name}}) that are replaced by viaduc (helloworld.py).
#! /usr/bin/env python3
from viaduc import Viaduc
class Presentation(Viaduc.Presentation):
title = 'hello world'
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
{{bootstrap_meta}}
{{bootstrap_css}}
<title>{{title}}</title>
</head>
<body>
<div class="jumbotron">
<h1>{{title}}</h1>
<p class="lead">Welcome to <em>Viaduc</em>, the simplest way of creating a GUI in python.</p>
</div>
{{bootstrap_js}}
</body>
</html>
'''
if __name__ == '__main__':
Viaduc(presentation=Presentation())
and we will obtain this
We have seen how Presentation can implement the GUI, but what about the interaction?
(temperature-converter.py)
#! /usr/bin/env python3
import sys
from viaduc import Viaduc
def fahrenheit_to_celsius(fahrenheit):
return round((5 / 9) * (float(fahrenheit) - 32), 2)
class Api(Viaduc.Api):
def convert(self, vals):
v = self.map_vals(vals)
if not v['_fahrenheit']:
raise ValueError('Enter a temperature')
return Viaduc.callback('showCelsius', {'celsius': fahrenheit_to_celsius(v['_fahrenheit'])})
class Presentation(Viaduc.Presentation):
width = 320
height = 468
title = 'temperature converter'
html = '''
<!-- copy file here -->
'''
file = "temperature-converter.html"
if __name__ == '__main__':
Viaduc(api=Api(), presentation=Presentation(), args=sys.argv + ['--frameless'])
Here we are also implementing API, which provides the means of interoperation between domains.
Another thing to note here is that instead of having the HTML as a string, we read it from a file to keep this example file smaller and focus on the essential parts.
When we execute it, we obtain this window. It's frameless as we are passing this extra argument to Viaduc.
Clicking the Convert button or pressing RETURN converts Fahrenheit's temperature to Celsius, invoking the convert() method. Viaduc automatically adds all the form values as vals that contain ids and values.
Once we convert the temperature using fahrenheit_to_celsius() we use the CALLBACK action to invoke a javascript method defined in temperature-converter.html to show the result.
We can also interact with the local filesystem reading and writing files. This editor shows these interactions.
Also shows how to use Bootstrap Material Design.
A more involved and practical example. android-device-viewer shows how easy is to provide a GUI and some interaction to other apps.
Using Culebratester2-public and the python client library Culebratester2-client communicates with an Android device to get the screenshot and send events (i.e. clicks). Therefore, you can interact with the physical device through its representation.
Also provides a context menu to be able to send BACK or quit the app.
Im case you don't need or want bootstrap to be included you can pass the option --no-bootstrap and viaduc won't
complain if the components are missing.
Viaduc(presentation=Presentation(), args=sys.argv + ['--no-bootstrap'])
See no-bootstrap.py.
In case you are into streamlit and want to use your app without a web browser, you can pass the option --with-streamlit=YOUR-APP.py or to see streamlit's demo you can use --with-streamlit=hello.
💁 viaduc >= 2.0.3 is required for this feature
Here is an example of the latter
See with-streamlit.py.
Also included in these examples, a columns demo
See with-streamlit-columns.py and columns.py.
If you use Claude Code (or any other agent reading a CLAUDE.md), you can teach it
how to create a stand-alone viaduc application by adding the following instructions to your CLAUDE.md.
## Python instructions
1. use python 3.12
2. for a streamlit app use this shebang on macOS
```python
#! /usr/bin/env streamlit run [--server.port=XXXX] ...
# streamlit python code here...
```
## Streamlit instructions
1. API docs can be found at https://docs.streamlit.io/
## Viaduc rules
REMEMBER: Viaduc is NOT an API client, it is a UI framework
1. viaduc is a python package to create simple UIs
2. When we are referring to "viaduc" (no final "t") we refer to https://github.com/dtmilano/viaduc
3. viaduc can be installed from pypi using `pip install viaduc`
4. to use viaduc and streamlit together there are some examples at
https://github.com/dtmilano/viaduc?tab=readme-ov-file#with-streamlit
5. to convert a streamlit app that runs in the browser to a standalone viaduc python app use this pattern,
where `APP.py` is the APP being converted:
```python
#! /usr/bin/env python3
import sys
from viaduc import Viaduc
class Presentation(Viaduc.Presentation):
width = 800
height = 600
title = "APP TITLE"
if __name__ == "__main__":
Viaduc(presentation=Presentation(), args=sys.argv + ["--with-streamlit=APP.py"])
```
6. the viaduc launcher and the streamlit app are two separate files: keep the streamlit code in `APP.py` and
put only the `Presentation` and the `Viaduc(...)` call in the launcher
7. viaduc runs `streamlit run` itself and connects to `http://localhost:8501`, so keep streamlit on its default
port when running under viaduc (the shebang's `--server.port` is only used when the app is run directly)
8. run the standalone app with `python3 launcher.py`, and add `--debug` to see viaduc's and streamlit's logs







