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
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)
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
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")
ui.label("Create a label")
ui.link("UiWizard", "https://ui-wizard.com")
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")
ui.markdown("""This is **Markdown**.""")
This is Markdown.
ui.html("UiWizard <strong>STRONG</strong>")
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")
box = ui.checkbox(name="checkbox")
ui.label(text="Checkbox", for_=box)
ui.datepicker(name="datepicker", value=datetime.now(timezone.utc))
toggle = ui.toggle(name="key_name")
ui.label("Enable feature", toggle)
items = ["UiWizard", "HTMX"]
ui.dropdown(name="selection", items=items, placeholder="Placeholder")
ui.range(name="Range", min=0, max=10, value=0, step=2)
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)
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")
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()
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"
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)
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 = [
{"col1": "dat1", "col2": "dat2"},
{"col1": "dat1", "col2": "dat2"}
]
df = pd.DataFrame(data)
ui.table.from_dataframe(df)
| col1 | col2 |
|---|---|
| dat1 | dat2 |
| dat1 | dat2 |
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)
| id | input | title | des | |
|---|---|---|---|---|
| 1 | This is input | Some title | Description | |
| 2 | Second input | Second title | Description |
data = [
{"col1": "dat1", "col2": "dat2"},
{"col1": "dat1", "col2": "dat2"}
]
df = pd.DataFrame(data)
ui.aggrid(df)
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")
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")
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")
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"})
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"
]
}
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")
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],
}
],
})
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],
}
],
})
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},
],
}
})