Starting…

pyweft-ui gallery

Every control from pyweft-ui, live. Change the theme at the top; the code under each section is what produced it.

Buttons

Clicked 0 times.

<ui.Button variant="primary" @click="save">Primary</ui.Button>
<ui.Button variant="danger" icon="trash">Delete</ui.Button>
<ui.Button :loading="busy" @click="work">Load</ui.Button>
<ui.Button disabled="true">Disabled</ui.Button>

Form fields

Each one is bound with @bind

name='' email='' quantity=1
size=None date='' notes=''
phone='' card=''
<ui.TextField label="Name" required="true" @bind="name" hint="Your full name" />
<ui.NumberField label="Quantity" min="1" max="99" @bind="quantity" />
<ui.Select label="Size" options="Small, Medium, Large" @bind="size" />
<ui.TextArea label="Notes" rows="3" @bind="notes" />
<ui.TextField label="Phone" mask="(999) 999-9999" @bind="phone" />

Validation

Form and rules: try submitting it empty

form = Form(
    email=[rules.required(), rules.email()],
    password=[rules.required(), rules.min_length(8)],
    confirm=rules.same_as("password", "The passwords don't match"),
    agreed=rules.accepted("Agree to the terms to continue"),
)

def submit(self):
    if self.form.check():      # every message shows; the first bad field gets focus
        ...

<ui.TextField ref="email" label="Email" @bind="email"
              :error="form.error('email')" @blur="form.touch('email')" />

Stepper

A checkout in steps; Next checks each one

<ui.Stepper :steps="steps" @bind="step" linear="true"
            @next="check(event)" @finish="place_order" finish_label="Place order">
  <If cond="step == 'account'"> ... </If>
  <ElseIf cond="step == 'delivery'"> ... </ElseIf>
</ui.Stepper>

steps = [("account", "Account"), ("delivery", "Delivery"),
         {"value": "gift", "label": "Gift message", "optional": True},
         ("review", "Review")]

def check(self, step):          # False keeps the stepper on this step
    form = {"account": self.account, "delivery": self.delivery}.get(step)
    return form.check() if form is not None else True

Choices

Plan

plan = 'pro', updates = True, agreed = False

<ui.RadioGroup label="Plan" :options="[('free', 'Free'), ('pro', 'Pro')]" @bind="plan" />
<ui.Checkbox label="Send me updates" @bind="updates" />
<ui.Switch label="Dark mode" @bind="dark" />

Autocomplete

Suggestions as you type

Results come from a (pretend) server
country=None  assignee=None  city=None
<ui.Autocomplete label="Country" :options="countries" @bind="country" />

<ui.Autocomplete label="Assign to" :options="people" @bind="assignee">
  <Fill name="option">
    <span class="avatar">{{ option['data']['initials'] }}</span>
    {{ option['label'] }} <small>{{ option['data']['email'] }}</small>
  </Fill>
</ui.Autocomplete>

<ui.Autocomplete label="City" :options="cities" match="none" :loading="searching"
                 @search="find_cities(event)" @bind="city" />

Cascade select

Nested options one level at a time; cities load as a country opens

Countries load their cities as they open
A group can be chosen too
city=None  category=None
<ui.CascadeSelect label="City" :options="places" @bind="city"
                  show_path="true" clearable="true" @load="load_cities(event)" />

places = [
    {"value": "au", "label": "Australia", "children": [
        {"value": "nsw", "label": "New South Wales", "children": [
            {"value": "syd", "label": "Sydney"}, ...]},
    ]},
    {"value": "ca", "label": "Canada", "children": None},   # loads when opened
]

Chips

Choose from a row, or type to add

Size
Interests
Filters
pythonui
Backspace in the empty box removes the last
size='Medium'  interests=['travel']
tags=['python', 'ui']  invitees=[]
<ui.Chips options="Small, Medium, Large" @bind="size" />
<ui.Chips :options="interests" multiple="true" @bind="chosen" />
<ui.Chips :options="filters" removable="true" selectable="false"
          @remove="drop_filter(event)" />

<ui.ChipInput label="Tags" @bind="tags" />
<ui.ChipInput label="Invite" :options="people" free="false" @bind="invitees">
  <Fill name="chip">{{ chip['data']['initials'] }} {{ chip['label'] }}</Fill>
</ui.ChipInput>

Button toggle

A joined row of buttons: one choice, or several

Range

The weft is the thread woven through the warp to make fabric.

Plan
full_width stretches the buttons evenly
layout='list'  range='Week'  styles=['bold']  align='left'
<ui.ButtonToggle options="Day, Week, Month" @bind="view" />

<ui.ButtonToggle aria_label="Text style" multiple="true" @bind="styles" :options="[
    {'value': 'bold', 'label': 'Bold', 'icon': 'bold', 'icon_only': True},
    {'value': 'italic', 'label': 'Italic', 'icon': 'italic', 'icon_only': True},
]" />

<ui.ButtonToggle label="Plan" options="Monthly, Yearly" full_width="true" @bind="plan" />

Sliders

Drag, or use the arrow keys

Thumbs can pass each other
Updates when released
volume=40  price=(100, 350)  rating=4  opacity=0.8
<ui.Slider label="Volume" @bind="volume" format="{}%" />
<ui.Slider label="Price" :min="0" :max="500" :step="10" range="true"
           @bind="price" format="${}" value_label="side" />
<ui.Slider label="Rating" :min="1" :max="5" ticks="true"
           value_label="always" marks="1, 3, 5" @bind="rating" />
<ui.Slider label="Opacity" :max="1" :step="0.05" lazy="true" @bind="opacity" />

Expansion panels

Open one, or a stack where one is open at a time

Speed

uv add pyweft pyweft-ui (or pip install), then pyweft new myapp.

Yes: pyweft run serves it, pyweft build makes a static site.

pyweft run --desktop opens a native window; pyweft build --desktop packages it.

delivery_open=False  street=''  speed='Standard'
faq='install'
<ui.Expansion title="Delivery" description="Address and speed" @bind="delivery_open">
  ... fields ...
  <Fill name="actions"><ui.Button @click="save">Save</ui.Button></Fill>
</ui.Expansion>

<ui.Accordion :options="faq" @bind="question">
  <p>{{ answers[panel] }}</p>
</ui.Accordion>

Dates and menus

Check-in
From today on
Stay
At least one night
Nothing chosen yet.
check_in=None
stay=None
<ui.DatePicker label="Check-in" @bind="check_in" :min="today" />
<ui.DateRangePicker label="Stay" @bind="stay" :min="today" min_days="1" />

<ui.Menu label="Actions" @select="act(event)" :items="[
    {'value': 'edit', 'label': 'Edit', 'icon': 'edit'},
    '-',
    {'value': 'delete', 'label': 'Delete', 'icon': 'trash', 'danger': True},
]" />

Calendars

Choosing a date, and showing events

MoTuWeThFrSaSu

A Calendar on the page, always open. Its month and year are buttons too.

picked_day=None
MonTueWedThuFriSatSun
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

Click an event, or an empty day to add one.

<ui.Calendar @bind="day" />

<ui.EventCalendar :events="events" @select="open(event)" @day="add(event)" />
<ui.EventCalendar :events="events" months="3" />   <!-- three months, one under another -->

events = [
    {"title": "Standup", "start": datetime.datetime(2026, 11, 3, 9, 30),
     "end": datetime.datetime(2026, 11, 3, 9, 45), "color": "blue"},
    {"title": "Release", "start": datetime.date(2026, 11, 7), "color": "red"},   # all day
    {"title": "Trip", "start": datetime.date(2026, 11, 11), "end": datetime.date(2026, 11, 13)},
]

Feedback

Heads up
A neutral message.
Saved. Your changes are live.
neutralprimaryactivependingfailed
<ui.Alert kind="success">Saved.</ui.Alert>
<ui.Alert kind="danger" title="Failed" dismissible="true" @close="hide()">...</ui.Alert>
<ui.Badge kind="success">active</ui.Badge>  <ui.Spinner />  <ui.Icon name="star" />

Progress

How far along: a bar or a ring, or just still working

0%
Files
Uploading0 of 24 files
Streaming35%
Waiting for the server
72%
<ui.Progress label="Uploading" :value="sent" :max="24"
             format="{} of 24 files" show_value="true" />
<ui.Progress label="Waiting for the server" />        <!-- no value: still working -->
<ui.Progress :value="35" :buffer="60" />
<ui.Progress shape="circle" :value="72" kind="warning" size="lg" />

Dialog

<ui.Button @click="confirming = True">Delete project</ui.Button>
<ui.Dialog title="Delete project?" @bind="confirming">
  <p>This can't be undone.</p>
  <Fill name="footer">
    <ui.Button @click="confirming = False">Cancel</ui.Button>
    <ui.Button variant="danger" @click="delete()">Delete</ui.Button>
  </Fill>
</ui.Dialog>

Bottom sheet

Slides up from the bottom: swipe it down, press Esc or tap outside

filters: Open, $0-500
<ui.BottomSheet title="Share" @bind="sharing" :items="[
    {'value': 'link', 'label': 'Copy link', 'icon': 'copy'},
    {'value': 'email', 'label': 'Email', 'description': 'Opens your mail app'},
]" @select="share(event)" />

<ui.BottomSheet title="Filters" @bind="filtering">
  ... any content ...
  <Fill name="actions">
    <ui.Button variant="primary" @click="filtering = False">Show results</ui.Button>
  </Fill>
</ui.BottomSheet>

Popover and tooltip

Your own content beside a button; short labels on hover

Made by
ALAda LovelaceOwner · 12 projects
(hover or Tab to it)
Bold (Ctrl+B)Italic (Ctrl+I)Download as PDFDelete forever: there is no undo
<ui.Popover label="Share" icon="send" title="Share this page" @bind="open">
  <ui.TextField label="Link" :value="link" readonly="true" />
  <ui.Button variant="primary" @click="copy">Copy link</ui.Button>
</ui.Popover>

<ui.Popover open_on="hover" variant="link" label="Ada Lovelace">
  <Fill name="trigger">@ada</Fill>
  Ada Lovelace · Owner · 12 projects
</ui.Popover>

<ui.Tooltip text="Bold (Ctrl+B)">
  <ui.Button variant="ghost" icon="bold" aria-label="Bold"></ui.Button>
</ui.Tooltip>

Tabs

Three people are working on this project.

Showing one day.

<ui.Tabs options="Overview, Activity, Settings" @bind="tab">
  <If cond="tab == 'Overview'"> ... </If>
  <ElseIf cond="tab == 'Activity'"> ... </ElseIf>
  <Else> ... </Else>
</ui.Tabs>

Table

Click a heading to sort, a row to pick it

1041Ada Lovelacepaid1,250.00
1043Alan Turingrefunded410.00
1042Grace Hopperpending89.90
1044Katherine Johnsonpaid72.50

Picked: nothing yet

<ui.Table :rows="orders" row_key="id" sortable="true" @select="open_order"
          :columns="[('id', '#'), 'customer', 'status', {'key': 'total', 'format': ',.2f'}]">
  <Fill name="cell">
    <If cond="column == 'status'"><ui.Badge>{{ text }}</ui.Badge></If>
    <Else>{{ text }}</Else>
  </Fill>
</ui.Table>

Charts

Dashboards, everyday charts and data science: hover, use the arrow keys, click the legend

Bars
Lines
Revenue$48.2k+12% this month
Revenue over 10 values: from 31 to 48, low 31, high 48
Value
131
233
332
436
538
637
741
844
943
1048
Orders1,284+4% this month
Orders over 10 values: from 102 to 152, low 96, high 152
Value
1102
2118
396
4131
5125
6140
7133
8129
9147
10152
Refunds37+9 this month
Refunds over 10 values: from 21 to 37, low 21, high 37
Value
121
225
324
422
528
630
727
833
935
1037
7.8
SalesClick a bar
Sales
OnlineStore
Jan$4,200$3,100
Feb$5,100$2,900
Mar$4,800$3,500
Apr$6,100$3,300
May$7,000$3,600
Jun$6,600$4,100

Nothing picked yet.

Languages used
Languages used
Value
JavaScript62%
Python51%
SQL51%
TypeScript38%
Java30%
C#27%
Go14%
Visits
Visits
DesktopMobileTablet
Mon820620120
Tue932732132
Wed901801101
Thu9341,034134
Fri1,290990190
Sat6101,120230
Sun5201,210210
Stacked area
Stacked area
DesktopMobileTablet
Mon820620120
Tue932732132
Wed901801101
Thu9341,034134
Fri1,290990190
Sat6101,120230
Sun5201,210210
WeatherTwo scales: a combo chart
Weather
RainTemperature
Jan784.2
Feb525.1
Mar618.3
Apr4511.6
May5015.2
Jun4618.4
Line styles
Line styles
PlanActualForecast
W14038–
W24547–
W35049–
W4555858
W560–63
W665–66
W770–72
Browsers
Browsers
ValueShare
Chrome64.7%65%
Safari18.6%19%
Edge5.2%5.2%
Firefox2.8%2.8%
Other8.7%8.7%
Spending
Spending
categoryValueShare
Rent$1,45059%
Food$52021%
Travel$31013%
Fun$1907.7%
Seats
Seats
ValueShare
Green4238%
Blue3532%
Red2321%
Other109.1%
Tickets by day
Tickets by day
ValueShare
Mon1821%
Tue2529%
Wed1214%
Thu911%
Fri2125%
Height and weight
Height and weight
SeriesHeight (cm)Weight (kg)
Under 3015249
Under 3015855
Under 3016354
Under 3016763
Under 3017164
Under 3017670
Under 3018173
Under 3018679
30 and over15558
30 and over16063
30 and over16566
30 and over17074
30 and over17477
30 and over17879
30 and over18388
30 and over18992
CountriesBubble size: population (millions)
Countries
SeriesIncome per person ($k)Life expectancyPopulation (m)
Life5282.138
Life3178.4120
Life867.2210
Life1472.564
Life4481.39
Life361.895
Life2376.9330
Life61836
Sign-up funnel
Sign-up funnel
ValueOf firstFrom previous
Visited the site12,400100%
Started sign-up4,10033%33%
Confirmed email2,65021%65%
Started a trial9807.9%37%
Paid3102.5%32%
As a funnel
As a funnel
ValueOf firstFrom previous
Visited the site12,400100%
Started sign-up4,10033%33%
Confirmed email2,65021%65%
Started a trial9807.9%37%
Paid3102.5%32%
Revenue to profit
Revenue to profit
ChangeTotal
Revenue$48,000
Cost of goods−$19,500$28,500
Salaries−$14,200$14,300
Marketing−$4,100$10,200
Other income+$2,300$12,500
Tax−$3,100$9,400
Profit$9,400
ForecastWith a 90% interval and a trend line
Forecast
ActualForecast
Jan120–
Feb132–
Mar128–
Apr141–
May150150
Jun–156
Jul–163
Aug–169
Sep–176
Trial resultsMean ± standard error
Trial results
Effect
Placebo1.2
5 mg2.9
10 mg4.4
20 mg5.1
Heights
Heights
FromToWomenMen
14014510
14515040
150155130
155160211
1601653910
1651705219
1701752425
175180541
180185141
185190019
19019503
19520001
Response timesBox plot, outliers as dots
Response times
nMinQ1MedianQ3MaxMean
Search60578399124.5210106.58
Checkout6074148177225449188.47
Login602652.56280.7513468.08
Upload60139204258.5310.5690274.17
The same, as violins
The same, as violins
nMinQ1MedianQ3MaxMean
Search60578399124.5210106.58
Checkout6074148177225449188.47
Login602652.56280.7513468.08
Upload60139204258.5310.5690274.17
Visits by hour
Visits by hour
060708091011121314151617181920212223
Mon5320283137334540454229322710630
Tue55191828383336403731292427171300
Wed0817213334384435453932321921900
Thu0418173230394541413527231817700
Fri05171622323238374038303022111300
Sat09202652555666666264333425121300
Sun0510264759676061666235321813500
What moves togetherCorrelation of car measures
What moves together
PricePowerWeightMpgDoors
Price1.000.970.77-0.720.07
Power0.971.000.81-0.770.12
Weight0.770.811.00-0.92-0.07
Mpg-0.72-0.77-0.921.000.24
Doors0.070.12-0.070.241.00
ACME shares
ACME shares
DateOpenHighLowCloseVolume
Mar 1$84.00$85.16$83.50$84.0621,000
Mar 2$84.06$84.53$83.20$84.2628,000
Mar 3$84.26$84.49$83.23$83.4728,000
Mar 4$83.47$83.87$79.39$80.5929,000
Mar 5$80.59$81.37$80.13$81.0630,000
Mar 6$81.06$82.25$80.67$81.6920,000
Mar 7$81.69$82.59$81.38$81.3822,000
Mar 8$81.38$84.38$81.22$83.0824,000
Mar 9$83.08$84.45$81.29$81.688,000
Mar 10$81.68$84.40$81.19$83.8011,000
Mar 11$83.80$86.01$83.18$85.8114,000
Mar 12$85.81$88.58$85.77$88.2626,000
Mar 13$88.26$88.39$87.28$87.4019,000
Mar 14$87.40$88.33$87.30$87.6222,000
Mar 15$87.62$89.43$86.69$88.3911,000
Mar 16$88.39$90.38$87.42$90.2528,000
Mar 17$90.25$91.86$90.16$91.6521,000
Mar 18$91.65$92.89$91.13$91.9929,000
Mar 19$91.99$92.46$91.21$91.4024,000
Mar 20$91.40$96.64$90.98$94.2520,000
Apr 1$94.25$94.36$93.20$93.4528,000
Apr 2$93.45$93.83$92.36$93.3715,000
Apr 3$93.37$94.32$92.66$93.9321,000
Apr 4$93.93$94.54$91.24$91.3625,000
Apr 5$91.36$92.39$88.62$89.8615,000
Apr 6$89.86$90.66$89.16$90.5816,000
Apr 7$90.58$93.32$89.28$93.2016,000
Apr 8$93.20$94.43$92.36$93.7418,000
Apr 9$93.74$93.84$91.45$92.1619,000
Apr 10$92.16$92.52$89.99$90.4920,000
Apr 11$90.49$94.03$89.44$93.5312,000
Apr 12$93.53$94.21$91.95$92.0826,000
Apr 13$92.08$92.52$91.05$92.5116,000
Apr 14$92.51$93.27$91.36$91.9715,000
Apr 15$91.97$93.62$91.86$92.8720,000
Apr 16$92.87$92.99$91.51$91.9430,000
Apr 17$91.94$92.75$89.13$89.8828,000
Apr 18$89.88$90.13$86.68$87.0730,000
Apr 19$87.07$87.67$86.52$87.3911,000
Apr 20$87.39$87.91$85.40$85.5815,000
May 1$85.58$87.26$84.84$86.5612,000
May 2$86.56$86.72$85.61$86.1113,000
May 3$86.11$86.44$82.01$83.2822,000
May 4$83.28$83.83$82.55$83.5717,000
May 5$83.57$85.08$82.09$84.5210,000
May 6$84.52$86.86$83.94$86.5828,000
May 7$86.58$89.03$85.92$88.8618,000
May 8$88.86$88.97$85.38$85.6623,000
May 9$85.66$85.80$81.53$82.5528,000
May 10$82.55$83.43$82.34$83.2229,000
May 11$83.22$83.78$81.14$82.2514,000
May 12$82.25$82.38$81.43$82.0116,000
May 13$82.01$82.42$79.25$79.5720,000
May 14$79.57$83.27$77.98$82.3224,000
May 15$82.32$82.69$81.59$81.9214,000
May 16$81.92$82.35$81.42$82.1226,000
May 17$82.12$82.96$81.46$82.9125,000
May 18$82.91$83.67$82.36$83.049,000
May 19$83.04$83.23$81.46$82.6323,000
May 20$82.63$84.35$82.02$83.5924,000
Sales by region
Sales by region
ValueShare
Europe › Germany$182k13.7%
Europe › France$140k10.6%
Europe › Italy$98k7.4%
Europe › Spain$76k5.7%
Europe › Sweden$41k3.1%
Americas › USA$260k19.6%
Americas › Brazil$88k6.6%
Americas › Canada$64k4.8%
Asia › Japan$150k11.3%
Asia › India$120k9.0%
Asia › Korea$58k4.4%
Oceania › Australia$49k3.7%
Where the money goes
Where the money goes
FromToValue
SalaryBudget$5,200
FreelanceBudget$1,300
BudgetHousing$1,900
BudgetFood$850
BudgetTransport$420
BudgetFun$630
BudgetSavings$2,700
SavingsIndex funds$1,800
SavingsEmergency fund$900
ContributionsA year of days
Contributions
DateValue
2025-11-240
2025-11-252
2025-11-260
2025-11-272
2025-11-282
2025-11-290
2025-11-300
2025-12-012
2025-12-021
2025-12-031
2025-12-041
2025-12-052
2025-12-060
2025-12-071
2025-12-083
2025-12-093
2025-12-101
2025-12-115
2025-12-125
2025-12-130
2025-12-140
2025-12-158
2025-12-161
2025-12-170
2025-12-180
2025-12-190
2025-12-201
2025-12-210
2025-12-220
2025-12-238
2025-12-240
2025-12-253
2025-12-260
2025-12-270
2025-12-280
2025-12-290
2025-12-300
2025-12-310
2026-01-015
2026-01-020
2026-01-030
2026-01-040
2026-01-052
2026-01-060
2026-01-072
2026-01-083
2026-01-095
2026-01-100
2026-01-110
2026-01-123
2026-01-131
2026-01-141
2026-01-150
2026-01-165
2026-01-170
2026-01-181
2026-01-193
2026-01-208
2026-01-210
2026-01-228
2026-01-232
2026-01-241
2026-01-250
2026-01-263
2026-01-270
2026-01-280
2026-01-290
2026-01-300
2026-01-311
2026-02-010
2026-02-020
2026-02-031
2026-02-040
2026-02-058
2026-02-061
2026-02-070
2026-02-081
2026-02-090
2026-02-102
2026-02-111
2026-02-128
2026-02-131
2026-02-140
2026-02-151
2026-02-168
2026-02-178
2026-02-180
2026-02-190
2026-02-200
2026-02-210
2026-02-220
2026-02-231
2026-02-240
2026-02-253
2026-02-261
2026-02-270
2026-02-280
2026-03-011
2026-03-025
2026-03-031
2026-03-040
2026-03-058
2026-03-061
2026-03-071
2026-03-080
2026-03-090
2026-03-100
2026-03-118
2026-03-123
2026-03-138
2026-03-141
2026-03-150
2026-03-161
2026-03-171
2026-03-185
2026-03-198
2026-03-200
2026-03-210
2026-03-220
2026-03-230
2026-03-240
2026-03-250
2026-03-263
2026-03-273
2026-03-281
2026-03-290
2026-03-301
2026-03-313
2026-04-013
2026-04-022
2026-04-030
2026-04-040
2026-04-050
2026-04-068
2026-04-073
2026-04-080
2026-04-093
2026-04-102
2026-04-111
2026-04-120
2026-04-133
2026-04-141
2026-04-150
2026-04-165
2026-04-172
2026-04-181
2026-04-190
2026-04-201
2026-04-210
2026-04-225
2026-04-238
2026-04-248
2026-04-250
2026-04-261
2026-04-272
2026-04-283
2026-04-293
2026-04-300
2026-05-013
2026-05-020
2026-05-031
2026-05-045
2026-05-053
2026-05-068
2026-05-078
2026-05-088
2026-05-090
2026-05-100
2026-05-113
2026-05-128
2026-05-138
2026-05-140
2026-05-158
2026-05-160
2026-05-170
2026-05-188
2026-05-198
2026-05-208
2026-05-213
2026-05-220
2026-05-231
2026-05-240
2026-05-251
2026-05-261
2026-05-270
2026-05-285
2026-05-291
2026-05-301
2026-05-310
2026-06-013
2026-06-022
2026-06-035
2026-06-042
2026-06-052
2026-06-060
2026-06-071
2026-06-081
2026-06-098
2026-06-103
2026-06-110
2026-06-128
2026-06-130
2026-06-140
2026-06-158
2026-06-168
2026-06-175
2026-06-185
2026-06-193
2026-06-200
2026-06-210
2026-06-221
2026-06-231
2026-06-243
2026-06-258
2026-06-268
2026-06-270
2026-06-281
2026-06-292
2026-06-302
2026-07-012
2026-07-028
2026-07-035
2026-07-040
2026-07-050
2026-07-060
2026-07-078
2026-07-080
2026-07-090
2026-07-105
2026-07-111
2026-07-121
2026-07-133
2026-07-141
2026-07-151
2026-07-160
2026-07-173
2026-07-181
2026-07-190
2026-07-203
2026-07-215
2026-07-221
2026-07-231
2026-07-242
2026-07-250
2026-07-260
2026-07-270
2026-07-280
2026-07-290
2026-07-305
2026-07-310
2026-08-010
2026-08-021
2026-08-032
2026-08-045
2026-08-053
2026-08-065
2026-08-071
2026-08-080
2026-08-090
2026-08-105
2026-08-115
2026-08-128
2026-08-131
2026-08-140
2026-08-150
2026-08-161
2026-08-170
2026-08-183
2026-08-195
2026-08-201
2026-08-210
2026-08-221
2026-08-230
2026-08-240
2026-08-258
2026-08-260
2026-08-270
2026-08-288
2026-08-290
2026-08-300
2026-08-310
2026-09-012
2026-09-020
2026-09-035
2026-09-040
2026-09-050
2026-09-061
2026-09-070
2026-09-080
2026-09-093
2026-09-102
2026-09-118
2026-09-121
2026-09-130
2026-09-143
2026-09-152
2026-09-165
2026-09-170
2026-09-180
2026-09-191
2026-09-201
2026-09-212
2026-09-228
2026-09-238
2026-09-240
2026-09-258
2026-09-260
2026-09-271
2026-09-280
2026-09-293
2026-09-305
2026-10-013
2026-10-022
2026-10-030
2026-10-040
2026-10-050
2026-10-062
2026-10-070
2026-10-081
2026-10-098
2026-10-100
2026-10-110
2026-10-128
2026-10-130
2026-10-148
2026-10-158
2026-10-161
2026-10-170
2026-10-180
2026-10-192
2026-10-200
2026-10-218
2026-10-220
2026-10-235
2026-10-240
2026-10-250
2026-10-262
2026-10-275
2026-10-285
2026-10-290
2026-10-300
2026-10-310
2026-11-010
2026-11-022
2026-11-033
2026-11-043
Skills
Skills
skillAdaGrace
Python96
SQL78
Design57
Testing86
Ops49
Writing75
<ui.BarChart :data="sales" x="month" y="online, store" title="Sales"
             stacked="true" format="${:,.0f}" @select="open_month(event)" />

<ui.LineChart :data="visits" x="day" y="desktop, mobile" curve="smooth" fill="true" />

<ui.BarChart :data="weather" x="month" y="rain" :series="[
    {'key': 'temp', 'type': 'line', 'axis': 'right', 'color': 'warning'}]" />

<ui.PieChart :data="{'Chrome': 64.7, 'Safari': 18.6, 'Other': 16.7}" />
<ui.PieChart :data="spend" label="category" value="amount" donut="true" center="total" />
<ui.PolarAreaChart :data="tickets" />
<ui.RadarChart :data="skills" x="skill" y="ada, grace" />

<ui.ScatterChart :data="people" x="height" y="weight" group="group" trend="true" />
<ui.ScatterChart :data="countries" x="income" y="life" size="people" label="name" />
<ui.Sparkline :data="[31, 33, 32, 36, 38, 41]" kind="area" />
<ui.Gauge :value="cpu" label="CPU" format="{:.0f}%" bands="60:success, 85:warning, 100:danger" />
<ui.FunnelChart :data="{'Visited': 12400, 'Signed up': 4100, 'Paid': 310}" />
<ui.WaterfallChart :data="bridge" label="item" value="amount" totals="Revenue, Profit" />

<ui.Histogram :data="people" y="height" group="group" curve="true" show_mean="true" />
<ui.BoxPlot :data="{'Search': times_a, 'Login': times_b}" violin="true" points="all" />
<ui.Heatmap :data="visits" x="hour" y="day" value="visits" />
<ui.CorrelationMatrix :data="cars" triangle="lower" />
<ui.CandlestickChart :data="prices" averages="5, 20" />
<ui.Treemap :data="sales" path="region, country" value="sales" />
<ui.SankeyChart :data="[('Salary', 'Budget', 5200), ('Budget', 'Savings', 2700)]" />
<ui.CalendarHeatmap :data="contributions" />
<ui.LineChart :data="forecast" x="month" :series="[
    {'key': 'forecast', 'lower': 'low', 'upper': 'high'}, {'key': 'actual', 'trend': True}]" />
<ui.BarChart :data="trials" x="dose" y="effect" :series="[{'key': 'effect', 'error': 'se'}]" />

sales = [{"month": "Jan", "online": 4200, "store": 3100}, ...]   # or a DataFrame

Tree

Folders load as they open; search; checkboxes with partly checked parents

src
app.weft
store.py
pyproject.toml

Selected: app

Permissions
Orders
View orders
Edit orders
Refund orders admin
Users
View users
Invite users
Delete users admin
Billing

3 granted: orders.read, orders.write, users.read

<ui.Tree :nodes="files" @bind="selected" :filter="query"
         @load="load_folder(event)" @activate="open_file(event)" />

<ui.Tree :nodes="permissions" checkable="true" expand_all="true" @bind="granted" />

files = [
    {"value": "src", "label": "src", "icon": "folder", "children": [
        {"value": "app", "label": "app.weft", "icon": "file"},
    ]},
    {"value": "docs", "label": "docs", "icon": "folder", "children": None},  # loads on open
]

async def load_folder(self, value):
    children = await fetch_folder(value)
    ...  # put them in that node's children list

Toasts

Messages that go away by themselves

Look
Snackbar: a compact dark bar at the bottom, one at a time

Items: 3

<ui.Toaster />   <!-- once, inside <ui.Theme> -->
<ui.Toaster look="snackbar" />   <!-- or as a snackbar -->

from pyweft_ui import toast
toast("Changes saved.", "success")
toast("Item deleted.", action="Undo", on_action=self.undo)

Layout

Row, Column, Grid, Card and Divider: no CSS needed

1
2
3
4
5
6
ALAda Lovelaceada@engine.org
GHGrace Hoppergrace@cobol.net
ATAlan Turingalan@bletchley.uk
or
<ui.Grid cols="3" gap="sm"> ... </ui.Grid>
<ui.Row justify="between"> ... </ui.Row>
<ui.Column gap="lg"> ... </ui.Column>

<ui.Divider />
<ui.Divider inset="3.1rem" spacing="none" />   <!-- under a list with avatars -->
<ui.Divider label="or" />
<ui.Divider vertical="true" />                 <!-- between buttons in a Row -->