Setup app

These examples expect that the user has setup the initial application like the following code snippet. The examples should be inserted into the home_page function.

It is recommended to use a virtual environment like poetry or pipenv.

Create a main.py file

from uiwiz import ui, UiwizApp
import uvicorn

app = UiwizApp()

@app.page("/")
async def home_page():
    ui.label("Hello world")

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

Run it

python main.py

Multiple Pages

You can define routes by using the PageRouter. This works just like FastAPI routers.

#doc_page.py
from uiwiz import PageRouter

docs_router = PageRouter(prefix="/docs")

@docs_router.page(path="/", title="Docs")
def docs_page():
    ui.label("Docs Page")
#main.py
from uiwiz import ui, UiwizApp
import uvicorn
from doc_page import docs_router

app = UiwizApp()

app.include_router(docs_router)

@app.page("/")
async def home_page():
    ui.label("Hello world")

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

Shared layout

UiWiz supports shared layouts for all pages. You can define a layout by inheriting from PageDefinition and setting the app.pagedefinitionclass to your layout class.

from uiwiz import PageDefinition, UiwizApp, ui
import uvicorn

class MyLayout(PageDefinition):
    def __init__(self) -> None:
        super().__init__()

    def content(self, _: ui.element) -> Optional[ui.element]:
        ui.element("header", "p-4 bg-blue-500 text-white", "This is the header applied to all pages")

app = UiwizApp(page_definition_class=MyLayout)

It can also be overriden per page by adding a pagedefinitionclass parameter to the page decorator.

from uiwiz import PageDefinition, UiwizApp, ui
import uvicorn

class MyLayout(PageDefinition):
    def content(self, _: ui.element) -> Optional[ui.element]:
        ui.element("header", "p-4 bg-blue-500 text-white", "This is the header applied only to this page")

app = UiwizApp()

@app.page("/", page_definition_class=MyLayout)
async def home_page():
    ui.label("Hello world")

The class has methods for header, body, content and footer that can be overridden to customize the layout. They are defined as such:

class PageDefinition:
    def header(self, header: Element) -> None:
        pass

    def body(self, body: Element) -> None:
        pass

    def content(self, content: Element) -> Optional[Element]:
        pass

    def footer(self, content: Element) -> None:
        pass

PageDefinition instance

It is also possible to access the PageDefinition instance for the current request. This allows you to set the title and the language attribute of the document by default. If a custom PageDefinition is used, it will be the instance of that class. This can be useful for setting page specific attributes dynamically.

from uiwiz import UiwizApp, ui, Page

app = UiwizApp()

@app.page("/")
async def home_page(page: Page):
    page.title = "My custom title"
    page.lang = "en"
    ui.label("Hello world")

Text elements

Labels for inputs

ui.label("Create a label")

Link

ui.link("UiWizard", "https://ui-wizard.com")
UiWizard
A link will often go in a menu
ui.link("UiWizard", "https://ui-wizard.com").classes("btn btn-ghost drawer-button font-normal")
with ui.element("ul").classes("menu menu-horizontal menu-md"):
    with ui.element("li"):
        ui.link("UiWizard", "https://ui-wizard.com")
UiWizard

Markdown

Render our favourite MD
ui.markdown("""This is **Markdown**.""")

This is Markdown.

HTML

Render raw html string. This should be used with care as it can be used to inject code if the content is user generated!
ui.html("UiWizard <strong>STRONG</strong>")
UiWizard STRONG

Control elements

Button

from uiwiz import ui, UiwizApp
app = UiwizApp()

@app.ui("/click/button")
def click_button():
    ui.toast("Clicked")

ui.button("Click me").on_click(click_button, swap="none")

Checkbox

box = ui.checkbox(name="checkbox")
ui.label(text="Checkbox", for_=box)

Checkbox

ui.datepicker(name="datepicker", value=datetime.now(timezone.utc))

Toggle

toggle = ui.toggle(name="key_name")
ui.label("Enable feature", toggle)

Dropdown

items = ["UiWizard", "HTMX"]
ui.dropdown(name="selection", items=items, placeholder="Placeholder")

Range

ui.range(name="Range", min=0, max=10, value=0, step=2)

Radio

with ui.col():
    with ui.row():
        wiz = ui.radio(name="radio", value="UiWizard")
        ui.label(text="UiWizard", for_=wiz)
    with ui.row():
        htmx = ui.radio(name="radio", value="HTMX")
        ui.label(text="HTMX", for_=htmx)

Input

To create an input that updates the UI, we need to use the on method. Usually an input will be wraped inside a form, but it is not required.

from uiwiz import ui, UiwizApp
app = UiwizApp()

class InputModel(BaseModel):
    input: str

@app.ui("/change/input")
def change_input(input_model: InputModel):
    ui.element(content=input_model.input)

ui.input(name="input", placeholder="Text").on("input", change_input, target=lambda: output.id)
output = ui.element()

To create a floating label, the placeholder must be set

ui.input(name="text", placeholder="Floating label").set_floating_label("Floating label")

Text area

from uiwiz import ui, UiwizApp
app = UiwizApp()

class InputModel(BaseModel):
    input: str

@app.ui("/change/input")
def change_input(input_model: InputModel):
    ui.element(content=input_model.input)

ui.textarea(name="input", placeholder="Text").on("input", change_input, target=lambda: output.id)
output = ui.element()

Upload

The upload is limited to 2048 bytes as it could be used to abuse server

from uiwiz import ui, UiwizApp
from fastapi import UploadFile
app = UiwizApp()

async def handle_upload(upload: UploadFile):
    data = upload.read()
    ui.toast(data)

ui.upload(name="upload").on_upload(on_upload=handle_upload, swap="none").attributes["accept"] = ".txt"

Forms

The name of the input element must match the name of the model attribute. Notice that the form will indicate the input, which failed validation. This works by leveraging pydantic and fastapi, which automatically sends a 422 response with the fields that failed validation.

from pydantic import BaseModel, Field
from uiwiz import ui, UiwizApp
app = UiwizApp()

class FormData(BaseModel):
    desc: str = Field(min_length=4)
    age: int
    # ..

# Without the @app.ui a hash of the function is
# used instead for the endpoint instead
@app.ui("/form/inputdata")
async def handle_form_request(input_form: FormData):
    ui.toast(str(input_form)).success()

with ui.form().on_submit(handle_form_request, swap="none"):
    ui.input("Description", name="desc")
    ui.input("Age", name="age")
    ui.button("submit")

It is also possible to have UiWiard create the form from a model

class FormData(BaseModel):
    desc: str = Field(min_length=4)
    age: int
    # ..

@app.ui("/form/inputdata")
async def handle_form_request(input_form: FormData):
    ui.toast(str(input_form)).success()

ui.modelForm(FormData).on_submit(handle_form_request)

It is also possible to customize the inputs from the model

class Base(BaseModel):
    desc: Annotated[str, UiAnno(ui.textarea)] = Field(min_length=4)
    age: int

ui.modelForm(
    Base,
    age={
        "ui": ui.dropdown,
        "placeholder": "Select",
        "items": [1, 2, 3, 4]
    }
).on_submit(handle_form_request)

Indicators

ui.spinner()
ui.spinner().ball()
ui.spinner().bars()
ui.spinner().infinity()
ui.spinner().dots()
ui.spinner().infinity().extra_small()
ui.spinner().infinity().small()
ui.spinner().infinity().medium()
ui.spinner().infinity().large()

The indicators can be used on actions performed by any element that requires a web request transaction

with ui.button("Request").on_click(handle_spinner, swap="none") as requester:
    ui.spinner(requester).infinity().medium()

Data and layout

Tables

data = [
    {"col1": "dat1", "col2": "dat2"},
    {"col1": "dat1", "col2": "dat2"}
]
df = pd.DataFrame(data)
ui.table.from_dataframe(df)
col1col2
dat1dat2
dat1dat2
Table from a Pydantic model. We are now able to create, read, delete, update
from uiwiz import PageRouter, ui
from uiwiz.models.model_handler import UiAnno
from pydantic import BaseModel

# Define the model
class TableData(BaseModel):
    id: Annotated[str, UiAnno(ui.hiddenInput)]
    input: str
    title: str
    des: str

# Create some data
table_data = {
    "1": TableData(id="1", input="This is input", title="Some title", des="Description"),
    "2": TableData(id="2", input="Second input", title="Second title", des="Description")
}

@router.ui("/table/save/row/")
async def save_row(model: TableData):
    ui.table.render_row(model, "id", edit_row, delete_row)

@router.ui("/table/display/row/{id}")
async def display_row(id: str):
    if id in table_data:
        ui.table.render_row(table_data[id], "id", edit_row, delete_row)

@router.ui("/table/edit/row/{id}")
async def edit_row(id: str):
    ui.table.render_edit_row(
        table_data[id],
        "id",
        save_row,
        display_row,
    )

@router.ui("/table/delete/row/{id}")
async def delete_row(id: str):
    # Delete the row
    pass

@router.ui("/table/add/row/")
async def add_row():
    ui.table.render_edit_row(
        TableData(id=str(len(table_data) + 1), input="", title="", des=""),
        "id",
        save_row,
        display_row
    )

ui.table(list(table_data.values()), "id").edit_row(edit_row).delete_row(delete_row).create_row(add_row)
idinputtitledes
1This is inputSome titleDescription
2Second inputSecond titleDescription
Aggrid - use this as a reference for implementing new client side components
The theme is fully integrated into the DaisyUI themes. It will even work with custom themes!
data = [
    {"col1": "dat1", "col2": "dat2"},
    {"col1": "dat1", "col2": "dat2"}
]
df = pd.DataFrame(data)
ui.aggrid(df)

Tabs

with ui.tabs():
    with ui.tab("Tab 1"):
        ui.element(content="This is tab 1")
    with ui.tab("Tab 2"):
        ui.element(content="This is tab 2")
This is tab 1
This is tab 2

Toast

The current version supports 4 different look and feel for toast.

Info, Error, Warning, Success

from uiwiz import ui, UiwizApp
app = UiwizApp()

@app.ui("/click/button")
def click_button():
    ui.toast("Clicked")
    ui.toast("Clicked").info()
    ui.toast("Clicked").success()
    ui.toast("Clicked").warning()
    ui.toast("Clicked").error()

ui.button("Click me").on_click(click_button, swap="none")
Want to have element inside the toast?
from uiwiz import ui, UiwizApp
app = UiwizApp()

@app.ui("/click/button/rich")
def click_button_rich():
    with ui.toast():
        ui.html("This is the github logo")
        svg = ui.html('''
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
    <path
        d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
''')
        # Style the gh logo to match the current theme
        svg.attributes["style"] = "fill: oklch(var(--bc)/1);"
        svg.classes("rounded-full")

ui.button("Click me").on_click(click_button_rich, swap="none")

Components

To create a component just create a method with the ui elements
def component(data: dict):
    with ui.element():
        ui.label("This is a component")
        for key, value in data.items():
            ui.html(f"Key: {key}, Value: {value}")

component({"key1": "value1", "key2": "value2"})
Key: key1, Value: value1
Key: key2, Value: value2

Dict

Display a dictionary
data = {
    "name": "Karl",
    "age": 20,
    "city": "Random",
    "friends": ["John", "Doe"]
}
ui.dict(data, copy_to_clipboard=True).border_classes("bg-base-200 w-full rounded-lg")
{
  "name":
"Karl"
,
  "age":
20
,
  "city":
"Random"
,
  "friends": [
    
"John"
,
    
"Doe"
  ]
}

Drawer

Create a drawer for navigation links
with ui.drawer(always_open=True, right=False) as drawer:
    with drawer.drawer_content():
        with ui.nav().classes("w-full navbar"):
            with ui.label(for_=drawer.drawer_toggle).classes("btn drawer-button lg:hidden"):
                ui.html(get_svg("menu"))

        ui.label("test1")
        with ui.footer():
            ui.label("some footer text")

    with drawer.drawer_side():
        with ui.element("li"):
            ui.link("Github", "https://github.com/Declow/uiwiz")

Echart

Create echarts for data visualization
ui.echart({
    "tooltip": {"trigger": "axis", "axisPointer": {"type": "line"}},
    "xAxis": {
        "type": "category",
        "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
    },
    "yAxis": {"type": "value"},
    "series": [
        {
            "name": "Sales",
            "type": "line",
            "data": [150, 230, 224, 218, 135, 147, 260],
        }
    ],
})
Update chart
from uiwiz import ui, UiwizApp
from random import randint

app = UiwizApp()

@app.post("/update/chart")
async def update_chart():
    return ui.echart.response(
        {
            "tooltip": {"trigger": "axis", "axisPointer": {"type": "line"}},
            "xAxis": {
                "type": "category",
                "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
            },
            "yAxis": {"type": "value"},
            "series": [
                {
                    "name": "Sales",
                    "type": "line",
                    "data": [randint(0, 300) for _ in range(7)],
                }
            ],
        }
    )

@app.post("/")
async def page_with_chart():
    ui.element(content="Update chart")
    ui.button("Update chart").on_click(update_chart, lambda: chart.id, swap="none")
    chart = ui.echart({
        "tooltip": {"trigger": "axis", "axisPointer": {"type": "line"}},
        "xAxis": {
            "type": "category",
            "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
        },
        "yAxis": {"type": "value"},
        "series": [
            {
                "name": "Sales",
                "type": "line",
                "data": [150, 230, 224, 218, 135, 147, 260],
            }
        ],
    })

Sankey Chart

ui.echart({
        "series": {
            "type": "sankey",
            "layout": "none",
            "emphasis": {"focus": "adjacency"},
            "data": [
                {"name": "a"},
                {"name": "b"},
                {"name": "a1"},
                {"name": "a2"},
                {"name": "b1"},
                {"name": "c"},
            ],
            "links": [
                {"source": "a", "target": "a1", "value": 5},
                {"source": "a", "target": "a2", "value": 3},
                {"source": "b", "target": "b1", "value": 8},
                {"source": "a", "target": "b1", "value": 3},
                {"source": "b1", "target": "a1", "value": 1},
                {"source": "b1", "target": "c", "value": 2},
            ],
        }
    })
GitHub