# Pyzed: Blocks > Pyzed is an open-source (MIT) Python framework for building native desktop GUIs. Python describes the interface; Iced, a GPU-accelerated GUI toolkit written in Rust, lays it out and draws it. It aims to be a frontend framework for desktop apps (mobile and web later), with 90+ components whose look is inspired by the Zed editor, themes, node-based GPU shaders, packaging for every desktop platform and Pyzed Design, a drag-and-drop visual designer in the spirit of Visual Basic, Qt Designer and Godot. ## Blocks ### Blocks > Ready-made Python desktop UI screens to copy: login, dashboard, settings, data table, chat, IDE layout, onboarding wizard, music player and kanban, built with Pyzed components. Source: https://albakore.github.io/pyzed/blocks/ Blocks are **complete screens** built from Pyzed's [components](https://albakore.github.io/pyzed/library/), in the spirit of shadcn/ui's blocks: open one, run it, copy the code into your app and change what you need. Each block is a single runnable file in [`examples/blocks/`](https://github.com/albakore/pyzed/tree/main/examples/blocks), tested in CI in light and dark themes, so the code on these pages always works with the current Pyzed. #### How to use a block 1. Open its page and look at the screenshot in light and dark (switch the site's theme). 2. Run it: `python examples/blocks/.py`, or copy the **Code** section into a file. 3. Keep what you need. Blocks are ordinary `pz.App` classes: their pieces are methods (`sidebar()`, `header()`...) that return widgets, so you can lift one into your own app as is. #### Make them yours - **Theme.** Set `theme = "Gruvbox Dark"` (or any [theme](https://albakore.github.io/pyzed/theming/themes/)) on the app, or derive one with your brand color: `pz.themes.get("One Dark").with_accent("#e11d48")`. Every block uses theme tokens (`"accent"`, `"panel"`, `"text_muted"`...), never fixed colors for its surfaces. - **Data.** The sample data is at the top of each file. Replace it with a database query, an HTTP call or files; slow work goes in a coroutine so the window never blocks (see [Async](https://albakore.github.io/pyzed/guides/async/)). - **Components.** When a piece grows its own state, turn it into a [`pz.Component`](https://albakore.github.io/pyzed/guides/components/) so it shows up in Pyzed Design's toolbox too. - **AI.** With the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) connected, ask your agent: *"Start from the dashboard block and make it show our GitHub issues"*. It can read every block with `read_example("blocks/dashboard")`. ### Login > A split sign-in screen for Python desktop apps: brand panel with a gradient, SSO and passkey buttons, email and password fields with validation. Source: https://albakore.github.io/pyzed/blocks/login/ A two-column sign-in screen: a gradient brand panel with features and a testimonial on a translucent card, and a form with single sign-on, passkey, email and password, *remember me* and inline validation. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/login.py ``` #### What it shows - Gradient background with `pz.Gradient` and a translucent glass-like card. - Validation on submit: a `ui.callout` shows the error, or the success. - Text links built with `.on(click=..., cursor="pointer")`. - Enter in the password field signs in. #### Components used | Component | Used for | | --- | --- | | [Input](https://albakore.github.io/pyzed/library/input/) | Email and password | | [Button](https://albakore.github.io/pyzed/library/button/) | Sign in, SSO and passkey | | [Checkbox](https://albakore.github.io/pyzed/library/checkbox/) | Keep me signed in | | [Callout](https://albakore.github.io/pyzed/library/callout/) | Errors and success | | [Avatar](https://albakore.github.io/pyzed/library/avatar/) | Testimonial author | | [Divider](https://albakore.github.io/pyzed/library/divider/) | The “or” separator | | [Heading](https://albakore.github.io/pyzed/library/heading/) | Title | | [Label](https://albakore.github.io/pyzed/library/label/) | Labels and links | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/login.py" """Login block: a split sign-in screen with a brand panel, single sign-on, passkey and an email form.""" import pyzed as pz from pyzed import ui def link(text, action): """A text that behaves like a link.""" return ui.label(text, color="accent", size="sm", weight="medium").on(click=action, cursor="pointer") FEATURES = [ ("rocket", "Deploy in seconds", "Push to main and your app is live worldwide."), ("shield-check", "Secure by default", "SSO, audit logs and encrypted secrets."), ("activity", "Observability built in", "Traces, metrics and logs in one place."), ] BRAND = pz.Gradient([(0.0, "#6366f1"), (0.55, "#8b5cf6"), (1.0, "#ec4899")], angle=135) class Login(pz.App): title = "Sign in" size = (1200, 760) def __init__(self): self.email = "ada@lovelace.dev" self.password = "analytical-engine" self.remember = True self.error = "" self.signed_in = False # -- actions --------------------------------------------------------------------------- def sign_in(self): if "@" not in self.email: self.error = "Enter a valid email address." elif len(self.password) < 8: self.error = "The password needs at least 8 characters." else: self.error, self.signed_in = "", True def forgot(self): self.error = "" if "@" in self.email else "Enter your email first." def sign_up(self): self.signed_in = False def set_email(self, value): self.email, self.error = value, "" def set_password(self, value): self.password, self.error = value, "" # -- view ------------------------------------------------------------------------------ def brand(self): quote = pz.column([ pz.text("“We replaced a 4,000-line Tkinter app with 900 lines of Pyzed. " "It starts faster and finally looks like it belongs in 2026.”", size=17, color="#ffffff", line_height=1.45), pz.row([ ui.avatar("ML", color="#f59e0b", size=34), pz.column([pz.text("Maya López", size=14, weight="semibold", color="#ffffff"), pz.text("Staff engineer, Northwind Labs", size=12.5, color="#e0e7ff")], spacing=2), ], spacing=10, align="center"), ], spacing=18) return pz.container( pz.column([ pz.row([ pz.container(pz.icon("zap", size=18, color="#ffffff"), padding=7, radius=9, bg="#ffffff33"), pz.text("Acme Cloud", size=17, weight="semibold", color="#ffffff"), ], spacing=10, align="center"), pz.vspace(), pz.column([self.feature(*f) for f in FEATURES], spacing=22), pz.vspace(), pz.container(quote, padding=22, radius=12, bg="#ffffff1c", border_color="#ffffff38", border_width=1), ]), width="fill", height="fill", padding=44, ).style(background=BRAND, radius=14) def feature(self, icon, title, text): return pz.row([ pz.container(pz.icon(icon, size=18, color="#ffffff"), padding=9, radius=10, bg="#ffffff26"), pz.column([pz.text(title, size=15, weight="semibold", color="#ffffff"), pz.text(text, size=13.5, color="#e0e7ff")], spacing=3), ], spacing=14, align="center") def form(self): social = pz.row([ ui.button("Single sign-on", self.sign_in, icon="building-2", size="lg", full_width=True), ui.button("Passkey", self.sign_in, icon="fingerprint", size="lg", full_width=True), ], spacing=10) separator = pz.row([ui.divider(), ui.muted("or continue with email", size="sm", wrap=False), ui.divider()], spacing=10, align="center") password_label = pz.row([ui.label("Password", size="sm", weight="medium"), pz.hspace(), link("Forgot password?", self.forgot)], align="center") fields = pz.column([ pz.column([ui.label("Email", size="sm", weight="medium"), ui.input(self.email, self.set_email, placeholder="you@company.com", icon="mail", size="lg", id="email")], spacing=6), pz.column([password_label, ui.input(self.password, self.set_password, placeholder="••••••••", icon="lock", size="lg", secure=True, on_submit=self.sign_in, id="password")], spacing=6), ui.checkbox(self.remember, lambda v: setattr(self, "remember", v), label="Keep me signed in"), ], spacing=16) status = [] if self.error: status = [ui.callout(self.error, tint="error", icon="circle-alert")] elif self.signed_in: status = [ui.callout("Signed in", f"Welcome back, {self.email}.", tint="success", icon="circle-check")] return pz.container( pz.column([ pz.column([ui.heading("Welcome back"), ui.muted("Sign in to your workspace to continue.")], spacing=6), social, separator, fields, *status, ui.button("Sign in", self.sign_in, variant="solid", size="lg", full_width=True, icon="log-in"), pz.row([ui.muted("Don't have an account?", size="sm"), link("Create one", self.sign_up)], spacing=6, align="center"), ], spacing=22, max_width=380), width="fill", height="fill", center=True, padding=32, ) def view(self): return pz.row([ pz.container(self.brand(), width="fill", height="fill", padding=12), self.form(), ], height="fill").style(background="background") if __name__ == "__main__": pz.run(Login) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/login")` and adapt it for you. ### Dashboard > An analytics dashboard in Python: sidebar navigation, KPI cards with sparklines, a revenue bar chart with ranges, a traffic donut and a sortable orders table. Source: https://albakore.github.io/pyzed/blocks/dashboard/ An admin dashboard: sidebar navigation with an upgrade card, a header with search and notifications, four KPI cards with trend lines, a revenue chart with a 7 days / 30 days / 12 months switch, traffic sources with a donut and a monthly goal, and a sortable table of recent orders. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/dashboard.py ``` #### What it shows - `ui.stat` cards: value, change and sparkline in one call. - `ui.bar_chart` and `ui.donut` take plain lists of numbers; colors come from the theme. - Custom cells in `ui.table`: avatars and status badges through `render=`. - Click a column header to sort; the search filters the orders as you type. #### Components used | Component | Used for | | --- | --- | | [Stat](https://albakore.github.io/pyzed/library/stat/) | KPI cards | | [Bar chart](https://albakore.github.io/pyzed/library/bar-chart/) | Revenue | | [Donut](https://albakore.github.io/pyzed/library/donut/) | Traffic sources | | [Table](https://albakore.github.io/pyzed/library/table/) | Recent orders | | [Card](https://albakore.github.io/pyzed/library/card/) | Every panel | | [Segmented](https://albakore.github.io/pyzed/library/segmented/) | Date range | | [List item](https://albakore.github.io/pyzed/library/list-item/) | Navigation | | [Search input](https://albakore.github.io/pyzed/library/search-input/) | Search | | [Progress](https://albakore.github.io/pyzed/library/progress/) | Monthly goal | | [Badge](https://albakore.github.io/pyzed/library/badge/) | Statuses | | [Avatar](https://albakore.github.io/pyzed/library/avatar/) | Customers | | [Sparkline](https://albakore.github.io/pyzed/library/sparkline/) | Trends | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/dashboard.py" """Dashboard block: sidebar navigation, KPI cards, a revenue chart, traffic sources and recent orders.""" import pyzed as pz from pyzed import ui NAV = [ ("layout-dashboard", "Overview"), ("chart-line", "Analytics"), ("shopping-cart", "Orders"), ("users", "Customers"), ("box", "Products"), ("credit-card", "Billing"), ] STATS = [ ("Revenue", "$48,290", 12.4, [18, 22, 19, 25, 27, 24, 31, 35]), ("Orders", "1,284", 8.1, [40, 38, 44, 47, 45, 52, 55, 58]), ("Customers", "9,431", 3.6, [70, 72, 71, 74, 76, 77, 79, 82]), ("Refund rate", "1.9%", -0.4, [3.1, 2.8, 2.9, 2.5, 2.4, 2.2, 2.0, 1.9]), ] REVENUE = { "7 days": ([5.2, 6.8, 6.1, 7.4, 8.9, 9.6, 8.2], ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]), "30 days": ([28, 31, 26, 35, 39, 42], ["W1", "W2", "W3", "W4", "W5", "W6"]), "12 months": ([31, 29, 36, 40, 38, 45, 51, 49, 55, 58, 62, 70], ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"]), } TRAFFIC = [("Organic search", 46, "accent"), ("Direct", 24, "success"), ("Social", 18, "warning"), ("Referral", 12, "error")] ORDERS = [ {"id": "#3210", "customer": "Olivia Martin", "product": "Pro plan · yearly", "status": "Paid", "amount": 1999.00}, {"id": "#3209", "customer": "Jackson Lee", "product": "Team plan · 5 seats", "status": "Pending", "amount": 450.00}, {"id": "#3208", "customer": "Isabella Nguyen", "product": "Starter plan", "status": "Paid", "amount": 99.00}, {"id": "#3207", "customer": "William Kim", "product": "Add-on · Analytics", "status": "Refunded", "amount": 39.00}, {"id": "#3206", "customer": "Sofia Davis", "product": "Pro plan · monthly", "status": "Paid", "amount": 199.00}, ] STATUS_TINT = {"Paid": "success", "Pending": "warning", "Refunded": "error"} class Dashboard(pz.App): title = "Dashboard" size = (1200, 760) def __init__(self): self.page = "Overview" self.range = "7 days" self.query = "" self.sort_by, self.descending = "Order", True # a column title def sort(self, key): self.descending = not self.descending if key == self.sort_by else False self.sort_by = key # -- pieces ---------------------------------------------------------------------------- def sidebar(self): items = [ui.list_item(label, lambda label=label: setattr(self, "page", label), icon=icon, selected=label == self.page, height=32) for icon, label in NAV] return pz.container( pz.column([ pz.row([pz.container(pz.icon("zap", size=16, color="#ffffff"), padding=6).style( background="accent", radius=8), ui.label("Acme", size="lg", weight="semibold")], spacing=10, align="center"), pz.column(items, spacing=2), pz.vspace(), ui.card(pz.column([ ui.label("Upgrade to Pro", weight="semibold"), ui.muted("Unlock forecasting and unlimited seats.", size="sm"), ui.button("Upgrade", lambda: setattr(self, "page", "Billing"), variant="solid", size="sm", full_width=True), ], spacing=8), padding=12), ui.list_item("Settings", None, icon="settings", height=32), ], spacing=20, height="fill"), width=220, height="fill", padding=(18, 12), ).style(background="panel") def header(self): return pz.row([ pz.column([ui.heading(self.page, level=2), ui.muted("Here is what happened in your store.")], spacing=2), pz.hspace(), pz.container(ui.search_input(self.query, lambda v: setattr(self, "query", v), placeholder="Search orders…"), width=240), ui.icon_button("bell", None, tooltip="Notifications"), ui.avatar("KV", size=30), ], spacing=12, align="center") def revenue(self): values, labels = REVENUE[self.range] return ui.card( pz.column([ pz.row([pz.text("$48.3k", size=22, weight="semibold"), ui.badge("+12.4%", tint="success", icon="trending-up")], spacing=10, align="center"), ui.bar_chart(values, labels=labels, width=470, height=140), ], spacing=12), title="Revenue", actions=[ui.segmented(list(REVENUE), self.range, lambda v: setattr(self, "range", v))], width="fill", ) def traffic(self): legend = [pz.row([ui.indicator(color, size=8), ui.label(name, size="sm"), pz.hspace(), ui.label(f"{share}%", size="sm", weight="medium")], spacing=8, align="center") for name, share, color in TRAFFIC] colors = [self.resolved_theme().color(c) for _, _, c in TRAFFIC] return ui.card( pz.column([ pz.row([ui.donut([s for _, s, _ in TRAFFIC], colors=colors, size=150, thickness=20, label="12.8k"), pz.column(legend, spacing=12, width="fill")], spacing=20, align="center"), ui.divider(), pz.column([pz.row([ui.label("Monthly goal", size="sm"), pz.hspace(), ui.muted("$48.3k of $60k", size="sm")]), ui.progress(0.805, height=6)], spacing=8), ], spacing=16), title="Traffic sources", width="fill", ) def orders(self): rows = [o for o in ORDERS if self.query.lower() in (o["customer"] + o["product"]).lower()] columns = [ ui.Column("Order", "id", width=80), ui.Column("Customer", "customer", render=lambda name: pz.row( [ui.avatar("".join(p[0] for p in name.split()), size=22, index=len(name)), ui.label(name)], spacing=8, align="center")), ui.Column("Product", "product"), ui.Column("Status", "status", width=110, render=lambda s: ui.badge(s, tint=STATUS_TINT[s])), ui.Column("Amount", "amount", width=100, align="end", render=lambda a: ui.label(f"${a:,.2f}")), ] return ui.card( ui.table(columns, rows, sort_by=self.sort_by, descending=self.descending, on_sort=self.sort, row_height=36), title="Recent orders", actions=[ui.button("View all", None, variant="ghost", size="sm", icon="arrow-right", icon_position="end")], width="fill", ) def view(self): stats = [ui.stat(title, value, delta=delta, trend=trend, width="fill") for title, value, delta, trend in STATS] body = pz.column([ self.header(), pz.row(stats, spacing=16), pz.row([pz.container(self.revenue(), width="fill:3"), pz.container(self.traffic(), width="fill:2")], spacing=16), self.orders(), ], spacing=20, padding=24) return pz.row([self.sidebar(), ui.divider(vertical=True), pz.scrollable(body, height="fill")], height="fill").style( background="background") if __name__ == "__main__": pz.run(Dashboard) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/dashboard")` and adapt it for you. ### Settings > A settings page for Python desktop apps: sectioned navigation, profile fields, theme and accent pickers, notification switches, a save bar and a danger zone with confirmation. Source: https://albakore.github.io/pyzed/blocks/settings/ A settings screen in the style of modern desktop apps: a section list on the left and grouped rows on the right, with profile fields, a language select, light / dark / system theme, accent swatches, a font size slider, notification switches and a danger zone that asks before deleting. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/settings.py ``` #### What it shows - `ui.settings_group` + `ui.field`: titled groups of rows with descriptions. - A sticky footer with *unsaved changes* and Save / Discard. - `ui.confirm` inside `ui.dialog_layer` for the destructive action. - Accent swatches with a ring on the selected one. #### Components used | Component | Used for | | --- | --- | | [Settings group](https://albakore.github.io/pyzed/library/settings-group/) | Groups of rows | | [Field](https://albakore.github.io/pyzed/library/field/) | Each setting | | [Input](https://albakore.github.io/pyzed/library/input/) | Name and email | | [Select](https://albakore.github.io/pyzed/library/select/) | Language | | [Segmented](https://albakore.github.io/pyzed/library/segmented/) | Theme | | [Color picker](https://albakore.github.io/pyzed/library/color-picker/) | Accent swatches | | [Slider](https://albakore.github.io/pyzed/library/slider/) | Font size | | [Switch](https://albakore.github.io/pyzed/library/switch/) | Notifications | | [Confirm](https://albakore.github.io/pyzed/library/confirm/) | Delete account | | [Dialog layer](https://albakore.github.io/pyzed/library/dialog-layer/) | The dialog over the page | | [List item](https://albakore.github.io/pyzed/library/list-item/) | Sections | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/settings.py" """Settings block: a sectioned settings page with profile, appearance, notifications and a danger zone.""" import pyzed as pz from pyzed import ui SECTIONS = [ ("user", "Profile"), ("palette", "Appearance"), ("bell", "Notifications"), ("shield", "Security"), ("credit-card", "Billing"), ("plug", "Integrations"), ] LANGUAGES = ["English", "Español", "Português", "Deutsch", "日本語"] ACCENTS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f97316", "#22c55e", "#14b8a6"] class Settings(pz.App): title = "Settings" size = (1200, 760) def __init__(self): self.section = "Profile" self.name = "Ada Lovelace" self.email = "ada@lovelace.dev" self.bio = "Writing the first programs for engines that don't exist yet." self.mode = "System" self.accent = ACCENTS[1] self.font_size = 14.0 self.language = "English" self.notify = {"mentions": True, "weekly": True, "product": False} self.saved = False self.confirm_delete = False def toggle(self, key): self.notify[key] = not self.notify[key] self.saved = False def discard(self): self.__init__() # back to the saved values def save(self): self.saved = True # -- pieces ---------------------------------------------------------------------------- def nav(self): items = [ui.list_item(label, lambda label=label: setattr(self, "section", label), icon=icon, selected=label == self.section, height=32) for icon, label in SECTIONS] return pz.column([ ui.label("Settings", size="xl", weight="semibold"), ui.search_input("", None, placeholder="Search settings…"), pz.column(items, spacing=2), ], spacing=16, width=230) def profile(self): return ui.settings_group("Profile", [ pz.row([ ui.avatar("AL", size=56), pz.column([ui.label(self.name, weight="semibold"), ui.muted(self.email, size="sm")], spacing=2, width="fill"), ui.button("Change photo", self.save, icon="upload", size="sm"), ], spacing=14, align="center"), ui.field("Display name", pz.container(ui.input(self.name, lambda v: setattr(self, "name", v)), width=320)), ui.field("Email", pz.container(ui.input(self.email, lambda v: setattr(self, "email", v), icon="mail"), width=320), description="Used for sign-in and receipts."), ui.field("Language", ui.select(LANGUAGES, self.language, lambda v: setattr(self, "language", v), width=320)), ], description="How other people see you across the workspace.") def appearance(self): swatches = pz.row([ pz.container(ui.color_swatch(color, size=22, on_press=lambda color=color: setattr(self, "accent", color)), padding=2).style(border=(2, "text" if color == self.accent else "#00000000"), radius=6) for color in ACCENTS], spacing=8) return ui.settings_group("Appearance", [ ui.field("Theme", ui.segmented(["Light", "Dark", "System"], self.mode, lambda v: setattr(self, "mode", v))), ui.field("Accent color", swatches, description="Buttons, links and focus rings."), ui.field("Interface font size", pz.row([ pz.container(ui.slider(self.font_size, lambda v: setattr(self, "font_size", round(v)), min=11, max=20, step=1), width=200), ui.label(f"{self.font_size:.0f} px", size="sm", mono=True), ], spacing=12, align="center")), ]) def notifications(self): rows = [ ("mentions", "Mentions and replies", "When someone mentions you or answers your comment."), ("weekly", "Weekly summary", "A digest of your projects every Monday."), ("product", "Product news", "New features and tips, about once a month."), ] return ui.settings_group("Notifications", [ ui.field(title, ui.switch(self.notify[key], lambda _=None, key=key: self.toggle(key)), description=text) for key, title, text in rows]) def danger(self): return ui.settings_group("Danger zone", [ ui.field("Delete account", ui.button("Delete account", lambda: setattr(self, "confirm_delete", True), variant="error", icon="trash-2"), description="Deletes your workspaces and data. This can't be undone."), ]) def view(self): footer = pz.row([ ui.muted("All changes saved" if self.saved else "You have unsaved changes", size="sm"), pz.hspace(), ui.button("Discard", self.discard, variant="ghost"), ui.button("Save changes", self.save, variant="solid", icon="check"), ], spacing=8, align="center") content = pz.column([ self.profile(), self.appearance(), self.notifications(), self.danger(), ], spacing=28, padding=(0, 24, 24, 0), max_width=760) page = pz.column([ pz.scrollable(content, height="fill"), ui.divider(), pz.container(footer, padding=(12, 0), max_width=736), ], width=780, height="fill") base = pz.row([self.nav(), page], spacing=40, padding=(28, 32, 12, 32), width="fill", height="fill").style( background="background") dialog = None if self.confirm_delete: dialog = ui.confirm("Your workspaces and data will be deleted for good.", lambda: setattr(self, "confirm_delete", False), lambda: setattr(self, "confirm_delete", False), title="Delete your account?", confirm_label="Delete", danger=True) return ui.dialog_layer(base, dialog, on_dismiss=lambda: setattr(self, "confirm_delete", False)) if __name__ == "__main__": pz.run(Settings) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/settings")` and adapt it for you. ### Data table > A data table with filters in Python: search, status chips, a role select, sortable columns, row selection, pagination, CSV export and an invite dialog. Source: https://albakore.github.io/pyzed/blocks/data-table/ A team members table: filter by name or email, toggle statuses with chips, pick a role, sort by any column, select rows with checkboxes, page through results, copy them as CSV and invite people from a dialog. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/data_table.py ``` #### What it shows - Filtering, sorting and paging done in plain Python, so it is easy to swap in a database query. - Two-line cells (avatar, name and email) through `ui.Column(render=...)`. - `pz.Command.copy` puts the CSV on the clipboard. - `ui.prompt` for the invite, with Enter to send. #### Components used | Component | Used for | | --- | --- | | [Table](https://albakore.github.io/pyzed/library/table/) | The table | | [Chip](https://albakore.github.io/pyzed/library/chip/) | Status filters | | [Search input](https://albakore.github.io/pyzed/library/search-input/) | Text filter | | [Select](https://albakore.github.io/pyzed/library/select/) | Role filter | | [Checkbox](https://albakore.github.io/pyzed/library/checkbox/) | Row selection | | [Badge](https://albakore.github.io/pyzed/library/badge/) | Statuses | | [Avatar](https://albakore.github.io/pyzed/library/avatar/) | Members | | [Facepile](https://albakore.github.io/pyzed/library/facepile/) | Team | | [Prompt](https://albakore.github.io/pyzed/library/prompt/) | Invite dialog | | [Icon button](https://albakore.github.io/pyzed/library/icon-button/) | Pagination and row menu | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/data_table.py" """Data table block: a team members table with search, status filters, sorting, selection and paging.""" import pyzed as pz from pyzed import ui PEOPLE = [ ("Olivia Martin", "olivia@acme.dev", "Owner", "Active", "Just now"), ("Jackson Lee", "jackson@acme.dev", "Admin", "Active", "5 min ago"), ("Isabella Nguyen", "isabella@acme.dev", "Editor", "Active", "1 hour ago"), ("William Kim", "will@acme.dev", "Viewer", "Invited", "—"), ("Sofia Davis", "sofia@acme.dev", "Editor", "Active", "Yesterday"), ("Liam Johnson", "liam@acme.dev", "Editor", "Suspended", "3 weeks ago"), ("Emma Wilson", "emma@acme.dev", "Admin", "Active", "2 hours ago"), ("Noah Brown", "noah@acme.dev", "Viewer", "Active", "4 days ago"), ("Ava Garcia", "ava@acme.dev", "Editor", "Invited", "—"), ("Lucas Rossi", "lucas@acme.dev", "Viewer", "Active", "Today"), ("Mia Fischer", "mia@acme.dev", "Editor", "Active", "Last week"), ("Ethan Clark", "ethan@acme.dev", "Viewer", "Suspended", "2 months ago"), ("Chloe Dubois", "chloe@acme.dev", "Admin", "Active", "Today"), ("Mateo Silva", "mateo@acme.dev", "Editor", "Active", "Yesterday"), ] ROWS = [{"id": i, "name": n, "email": e, "role": r, "status": s, "seen": seen} for i, (n, e, r, s, seen) in enumerate(PEOPLE)] BY_NAME = {r["name"]: r for r in ROWS} STATUS_TINT = {"Active": "success", "Invited": "info", "Suspended": "error"} ROLES = ["All roles", "Owner", "Admin", "Editor", "Viewer"] PAGE_SIZE = 9 SORT_KEYS = {"Member": "name", "Role": "role", "Status": "status", "Last active": "seen"} class DataTable(pz.App): title = "Team members" size = (1200, 760) def __init__(self): self.query = "" self.statuses = {"Active", "Invited"} self.role = "All roles" self.sort_by, self.descending = "Member", False # a column title self.selected = {1, 4} self.page = 0 self.notice = "" self.inviting, self.invite_email = False, "" # -- data ------------------------------------------------------------------------------ def visible(self): q = self.query.lower() rows = [r for r in ROWS if r["status"] in self.statuses and (self.role == "All roles" or r["role"] == self.role) and (q in r["name"].lower() or q in r["email"].lower())] return rows # -- actions --------------------------------------------------------------------------- def toggle_status(self, status): self.statuses ^= {status} self.page = 0 def toggle_row(self, row_id): self.selected ^= {row_id} def sort(self, key): self.descending = not self.descending if key == self.sort_by else False self.sort_by = key def export(self): rows = self.visible() self.notice = f"Copied {len(rows)} rows as CSV" lines = ["name,email,role,status"] + [f"{r['name']},{r['email']},{r['role']},{r['status']}" for r in rows] return pz.Command.copy("\n".join(lines)) def invite(self): if "@" in self.invite_email: self.notice = f"Invitation sent to {self.invite_email}" self.inviting, self.invite_email = False, "" def set_query(self, value): self.query, self.page = value, 0 # -- view ------------------------------------------------------------------------------ def person(self, row): return pz.row([ ui.avatar("".join(p[0] for p in row["name"].split()), size=28, index=row["id"]), pz.column([ui.label(row["name"], weight="medium"), ui.muted(row["email"], size="sm")], spacing=1), ], spacing=10, align="center") def toolbar(self): chips = [ui.chip(f"{status} {sum(r['status'] == status for r in ROWS)}", on_press=lambda status=status: self.toggle_status(status), selected=status in self.statuses, icon="circle-dot" if status in self.statuses else None) for status in STATUS_TINT] return pz.row([ pz.container(ui.search_input(self.query, self.set_query, placeholder="Filter by name or email…"), width=300), *chips, ui.select(ROLES, self.role, lambda v: setattr(self, "role", v), width=140), pz.hspace(), ui.button("Export", self.export, icon="download", variant="ghost"), ui.button("Invite member", lambda: setattr(self, "inviting", True), icon="user-plus", variant="solid"), ], spacing=8, align="center") def table(self, rows): columns = [ ui.Column("", "id", width=36, sortable=False, render=lambda i: ui.checkbox(i in self.selected, lambda _=None, i=i: self.toggle_row(i))), ui.Column("Member", "name", width=("fill", 3), render=lambda name: self.person(BY_NAME[name])), ui.Column("Role", "role", width=("fill", 1), render=lambda role: ui.label(role, color="text_muted")), ui.Column("Status", "status", width=("fill", 1), render=lambda s: ui.badge(s, tint=STATUS_TINT[s])), ui.Column("Last active", "seen", width=("fill", 1), render=lambda s: ui.muted(s)), ui.Column("", "id", width=44, sortable=False, render=lambda i: ui.icon_button("ellipsis", None, size="sm", tooltip="More")), ] return ui.table(columns, rows, sort_by=self.sort_by, descending=self.descending, on_sort=self.sort, row_height=52) def view(self): rows = self.visible() pages = max(1, -(-len(rows) // PAGE_SIZE)) page_rows = sorted(rows, key=lambda r: r[SORT_KEYS[self.sort_by]], reverse=self.descending) page_rows = page_rows[self.page * PAGE_SIZE:(self.page + 1) * PAGE_SIZE] start = self.page * PAGE_SIZE + 1 if rows else 0 footer = pz.row([ ui.muted(self.notice or f"{len(self.selected)} selected · showing {start}–{start + len(page_rows) - 1 if rows else 0} " f"of {len(rows)}", size="sm"), pz.hspace(), ui.icon_button("chevron-left", lambda: setattr(self, "page", max(0, self.page - 1)), disabled=self.page == 0, variant="filled"), *[ui.button(str(n + 1), lambda n=n: setattr(self, "page", n), size="sm", variant="solid" if n == self.page else "ghost") for n in range(pages)], ui.icon_button("chevron-right", lambda: setattr(self, "page", min(pages - 1, self.page + 1)), disabled=self.page >= pages - 1, variant="filled"), ], spacing=6, align="center") page = pz.column([ pz.row([ pz.column([ui.heading("Team members", level=2), ui.muted("Manage who can access the Acme workspace and what they can do.")], spacing=4), pz.hspace(), ui.facepile(["OM", "JL", "IN", "WK", "SD", "LJ"], size=28), ], align="center"), self.toolbar(), ui.card(pz.column([self.table(page_rows), ui.divider(), pz.container(footer, padding=(4, 4, 0, 4))], spacing=8), padding=8), ], spacing=20, padding=28, height="fill").style(background="background") dialog = None if self.inviting: dialog = ui.prompt("Invite a member", self.invite_email, lambda v: setattr(self, "invite_email", v), self.invite, lambda: setattr(self, "inviting", False), message="They get an email with a link to join the workspace.", placeholder="name@company.com", submit_label="Send invite", id="invite") return ui.dialog_layer(page, dialog, on_dismiss=lambda: setattr(self, "inviting", False)) if __name__ == "__main__": pz.run(DataTable) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/data_table")` and adapt it for you. ### Chat > A team chat UI in Python: channels with unread counts, direct messages with presence, grouped messages with Markdown, attachments, reactions and a rich composer. Source: https://albakore.github.io/pyzed/blocks/chat/ A messenger in the style of Slack or Discord: channels with unread counters, direct messages with presence dots, a thread that groups consecutive messages, Markdown, file attachments, reactions and a composer with formatting buttons. For an AI chat that streams answers see [Building AI apps](https://albakore.github.io/pyzed/ai/ai-apps/). The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/chat.py ``` #### What it shows - Consecutive messages from the same person are grouped under one avatar. - `ui.markdown` renders bold, code and links in messages. - `pz.scrollable(anchor_bottom=True)` keeps the newest message in view. - Enter sends and scrolls to the end with `pz.Command.scroll_to_end`. #### Components used | Component | Used for | | --- | --- | | [Markdown](https://albakore.github.io/pyzed/library/markdown/) | Message text | | [Avatar](https://albakore.github.io/pyzed/library/avatar/) | People | | [Indicator](https://albakore.github.io/pyzed/library/indicator/) | Presence | | [Count badge](https://albakore.github.io/pyzed/library/count-badge/) | Unread counts | | [List item](https://albakore.github.io/pyzed/library/list-item/) | Channels and people | | [List header](https://albakore.github.io/pyzed/library/list-header/) | Sidebar sections | | [Chip](https://albakore.github.io/pyzed/library/chip/) | Reactions | | [Input](https://albakore.github.io/pyzed/library/input/) | Composer | | [Facepile](https://albakore.github.io/pyzed/library/facepile/) | Channel members | | [Scrollable](https://albakore.github.io/pyzed/library/scrollable/) | The thread | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/chat.py" """Chat block: a team messenger with channels, direct messages, a message thread and a composer.""" import pyzed as pz from pyzed import ui CHANNELS = [("general", 0), ("design", 3), ("engineering", 12), ("releases", 0), ("random", 1)] PEOPLE = [("Maya López", "online"), ("Jackson Lee", "online"), ("Isabella Nguyen", "away"), ("William Kim", "offline")] PRESENCE = {"online": "success", "away": "warning", "offline": "text_disabled"} MESSAGES = [ ("Maya López", "09:12", "Morning! The new onboarding flow is on staging. Can someone give it a spin?", None), ("Jackson Lee", "09:14", "On it. First impression: **much** faster than the old one.", None), ("Jackson Lee", "09:15", "One nit: the progress bar jumps from step 2 to 4 when you skip the team invite.", None), ("Isabella Nguyen", "09:21", "Here's the build size before and after the switch to Pyzed:", ("build-report.pdf", "2.4 MB")), ("Maya López", "09:24", "Good catch, I'll fix the skip logic. Shipping it with `v2.3.1` this afternoon.", None), ] def initials(name): return "".join(part[0] for part in name.split()) class Chat(pz.App): title = "Messages" size = (1200, 760) def __init__(self): self.channel = "design" self.draft = "Looks great! Let's demo it in Friday's review" self.messages = list(MESSAGES) self.reactions = {2: {"👍": 3, "🎯": 1}, 4: {"🚀": 4}} def send(self): if self.draft.strip(): self.messages.append(("Kevin Vargas", "09:30", self.draft.strip(), None)) self.draft = "" return pz.Command.scroll_to_end("chat-thread") # -- sidebar --------------------------------------------------------------------------- def sidebar(self): channels = [ui.list_item(f"# {name}", lambda name=name: setattr(self, "channel", name), selected=name == self.channel, height=30, end=ui.count_badge(unread) if unread else None) for name, unread in CHANNELS] people = [ui.list_item(name, None, height=30, start=pz.stack([ ui.avatar(initials(name), size=20, index=i), pz.container(ui.indicator(PRESENCE[status], size=7), width=22, height=22, align_x="end", align_y="end")])) for i, (name, status) in enumerate(PEOPLE)] return pz.column([ pz.row([ui.label("Acme", size="lg", weight="semibold"), pz.hspace(), ui.icon_button("square-pen", None, tooltip="New message")], align="center"), ui.search_input("", None, placeholder="Jump to…"), pz.column([ui.list_header("Channels", end=ui.icon_button("plus", None, size="sm")), *channels], spacing=2), pz.column([ui.list_header("Direct messages"), *people], spacing=2), ], spacing=16, width=250, height="fill", padding=(16, 12)).style(background="panel") # -- thread ---------------------------------------------------------------------------- def message(self, index, author, time, text, attachment): previous = self.messages[index - 1][0] if index else None body = [ui.markdown(text)] if attachment: name, size = attachment body.append(pz.row([ pz.container(pz.icon("file-text", size=18, color="#ffffff"), padding=8).style( background="error", radius=8), pz.column([ui.label(name, weight="medium"), ui.muted(f"PDF · {size}", size="sm")], spacing=1), pz.hspace(width=24), ui.icon_button("download", None, tooltip="Download"), ], spacing=10, align="center").style(background="surface", border=(1, "border_variant"), radius=10, padding=10, width="shrink")) reactions = self.reactions.get(index) if reactions: body.append(pz.row([ui.chip(f"{emoji} {count}", on_press=None) for emoji, count in reactions.items()], spacing=6)) if author == previous: # a follow-up message: no avatar or name return pz.row([pz.space(width=36), pz.column(body, spacing=6, width="fill")], spacing=12, padding=(2, 20)) names = [a for a, _ in PEOPLE] header = pz.row([ui.label(author, weight="semibold"), ui.muted(time, size="sm")], spacing=8, align="end") return pz.row([ ui.avatar(initials(author), size=36, index=names.index(author) if author in names else 5), pz.column([header, *body], spacing=6, width="fill"), ], spacing=12, padding=(10, 20, 2, 20)) def composer(self): return pz.container( pz.column([ ui.input(self.draft, lambda v: setattr(self, "draft", v), placeholder=f"Message #{self.channel}", on_submit=self.send, borderless=True, id="composer"), pz.row([ ui.icon_button("plus", None, tooltip="Attach"), ui.icon_button("bold", None), ui.icon_button("italic", None), ui.icon_button("code", None), ui.icon_button("smile", None, tooltip="Emoji"), ui.icon_button("at-sign", None, tooltip="Mention"), pz.hspace(), ui.muted("Enter to send", size="sm"), ui.icon_button("send-horizontal", self.send, variant="solid" if self.draft else "ghost", tooltip="Send"), ], spacing=2, align="center"), ], spacing=6), padding=10, width="fill", ).style(background="surface", border=(1, "border"), radius=12) def view(self): header = pz.row([ ui.label(f"# {self.channel}", size="lg", weight="semibold"), ui.muted("Product design, reviews and handoff", size="sm"), pz.hspace(), ui.facepile(["ML", "JL", "IN", "WK", "KV", "SD", "AB"], size=24, max_shown=4), ui.icon_button("phone", None, tooltip="Call"), ui.icon_button("pin", None, tooltip="Pinned"), ui.icon_button("panel-right", None, tooltip="Details"), ], spacing=10, align="center", padding=(12, 20)) divider = pz.row([ui.divider(), ui.badge("Today", tint="accent", variant="outline"), ui.divider()], spacing=10, align="center", padding=(8, 20)) thread = pz.scrollable( pz.column([divider, *[self.message(i, *m) for i, m in enumerate(self.messages)]], spacing=4), height="fill", id="chat-thread", anchor_bottom=True) main = pz.column([header, ui.divider(), thread, pz.container(self.composer(), padding=(8, 20, 20, 20))], width="fill", height="fill").style(background="editor") return pz.row([self.sidebar(), ui.divider(vertical=True), main], height="fill") if __name__ == "__main__": pz.run(Chat) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/chat")` and adapt it for you. ### File explorer and IDE > An IDE layout in Python, like a modern code editor: project tree, editor tabs, breadcrumbs, a code editor with minimap and git markers, terminal tabs, an outline and a status bar. Source: https://albakore.github.io/pyzed/blocks/file-explorer/ A code editor window built with `ui.workspace`: a title bar with project and branch, a filterable project tree with git status colors, editor tabs and breadcrumbs, the code editor with syntax highlighting, git markers and a minimap, a terminal with tabs, an outline panel and a status bar. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/file_explorer.py ``` #### What it shows - `ui.workspace` places the docks; they can be resized by dragging. - `ui.TreeNode.from_paths` builds the tree from a list of paths, with git statuses. - `ui.Tab.for_file` picks the icon and color of each file type. - `ui.terminal` understands ANSI colors. #### Components used | Component | Used for | | --- | --- | | [Workspace](https://albakore.github.io/pyzed/library/workspace/) | The window layout | | [Tree](https://albakore.github.io/pyzed/library/tree/) | Project files | | [Tab bar](https://albakore.github.io/pyzed/library/tab-bar/) | Open files | | [Breadcrumbs](https://albakore.github.io/pyzed/library/breadcrumbs/) | Path | | [Editor](https://albakore.github.io/pyzed/library/editor/) | Code | | [Terminal](https://albakore.github.io/pyzed/library/terminal/) | Terminal | | [Title bar](https://albakore.github.io/pyzed/library/title-bar/) | Title bar | | [Status bar](https://albakore.github.io/pyzed/library/status-bar/) | Status bar | | [Toolbar](https://albakore.github.io/pyzed/library/toolbar/) | Editor toolbar | | [Panel header](https://albakore.github.io/pyzed/library/panel-header/) | Panel titles | | [List item](https://albakore.github.io/pyzed/library/list-item/) | Outline | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/file_explorer.py" """IDE layout block: a project tree, editor tabs with breadcrumbs, a terminal, an outline panel and a status bar.""" import pyzed as pz from pyzed import ui FILES = { "src/app/main.py": '''"""Acme Cloud desktop client.""" import pyzed as pz from pyzed import ui from .api import Client from .views import Sidebar, Deployments class AcmeApp(pz.App): title = "Acme Cloud" size = (1280, 800) def __init__(self): self.client = Client.from_env() self.deployments = [] self.selected = None def on_start(self): return self.refresh() async def refresh(self): return ("loaded", await self.client.deployments()) def update(self, message): kind, data = message if kind == "loaded": self.deployments = data def view(self): return ui.workspace( Deployments(items=self.deployments), left=Sidebar(selected=self.selected), ) if __name__ == "__main__": pz.run(AcmeApp) ''', "src/app/api.py": "import httpx\n\n\nclass Client:\n ...\n", "src/app/views.py": "import pyzed as pz\n", "src/app/__init__.py": "", "tests/test_app.py": "def test_refresh():\n ...\n", "pyproject.toml": "[project]\nname = \"acme-cloud\"\n", "README.md": "# Acme Cloud\n", ".gitignore": "__pycache__/\n", } STATUS = {"src/app/main.py": "modified", "src/app/views.py": "created", "README.md": "modified"} OUTLINE = [("class", "AcmeApp", 0), ("variable", "title", 1), ("variable", "size", 1), ("method", "__init__", 1), ("method", "on_start", 1), ("method", "refresh", 1), ("method", "update", 1), ("method", "view", 1)] OUTLINE_ICON = {"class": ("box", "warning"), "method": ("square-function", "accent"), "variable": ("variable", "info")} TERMINAL = ("$ uv run pytest -q\n" "\x1b[32m........................................\x1b[0m [100%]\n" "\x1b[32m40 passed\x1b[0m in 1.84s\n" "$ pyzed dev src/app/main.py\n" "\x1b[36m●\x1b[0m watching src/ for changes · hot reload on\n") class FileExplorer(pz.App): title = "acme-cloud — main.py" size = (1200, 760) def __init__(self): self.tree = ui.TreeNode.from_paths(list(FILES), status=STATUS) self.expanded = {"src", "src/app", "tests"} self.open = ["src/app/main.py", "src/app/api.py", "README.md"] self.active = "src/app/main.py" self.texts = dict(FILES) self.cursor = (24, 18) self.query = "" # -- actions --------------------------------------------------------------------------- def toggle(self, node_id): self.expanded ^= {node_id} def select(self, node_id): if node_id in FILES: if node_id not in self.open: self.open.append(node_id) self.active = node_id def close(self, path): self.open.remove(path) if self.active == path: self.active = self.open[-1] if self.open else None def edit(self, text): self.texts[self.active] = text # -- view ------------------------------------------------------------------------------ def project(self): return pz.column([ ui.panel_header("acme-cloud", [ui.icon_button("file-plus", None, size="xs", tooltip="New file"), ui.icon_button("folder-plus", None, size="xs", tooltip="New folder"), ui.icon_button("list-collapse", lambda: setattr(self, "expanded", set()), size="xs", tooltip="Collapse all")]), pz.container(ui.search_input(self.query, lambda v: setattr(self, "query", v), placeholder="Filter files…", size="sm"), padding=(4, 8)), ui.tree(self.tree, expanded=self.expanded, selected=self.active, on_toggle=self.toggle, on_select=self.select), ]) def outline(self): rows = [] for kind, name, depth in OUTLINE: icon, color = OUTLINE_ICON[kind] rows.append(ui.list_item(name, None, icon=icon, icon_color=self.resolved_theme().color(color), indent=depth * 14, height=26, selected=name == "refresh")) return pz.column([ui.panel_header("Outline", [ui.icon_button("arrow-down-a-z", None, size="xs")], icon="list-tree"), pz.column(rows, padding=(4, 4))]) def editor(self): if self.active is None: return ui.empty_state("No open files", "Pick a file in the project panel.", icon="file") tabs = [ui.Tab.for_file(p, dirty=p == "src/app/main.py", status=STATUS.get(p)) for p in self.open] parts = self.active.split("/") return pz.column([ ui.tab_bar(tabs, self.active, on_select=self.select, on_close=self.close, end=[ui.icon_button("columns-2", None, size="sm", tooltip="Split right")]), ui.toolbar([ui.breadcrumbs([("folder", parts[0]), *parts[1:], "AcmeApp", "refresh"])], [ui.icon_button("play", None, size="sm", tooltip="Run"), ui.icon_button("sparkles", None, size="sm", tooltip="Inline assist")]), ui.editor("ide-editor", self.texts[self.active], path=self.active, on_change=self.edit, markers={19: "modified", 20: "modified", 24: "created"}, minimap=True), ]) def view(self): line, col = self.cursor return ui.workspace( self.editor(), title_bar=ui.title_bar( [ui.title_button("acme-cloud", chevron=True), ui.title_button("feat/deployments", icon="git-branch", muted=True)], [ui.icon_button("bell", None, size="sm"), ui.avatar("KV")], center=ui.title_button("Search files…", icon="search", muted=True)), left=self.project(), right=self.outline(), bottom=pz.column([ ui.tab_bar([ui.Tab("zsh", "zsh", icon="terminal"), ui.Tab("tests", "tests", icon="flask-conical")], "zsh", end=[ui.icon_button("plus", None, size="sm"), ui.icon_button("x", None, size="sm")]), ui.terminal(TERMINAL), ]), status_bar=ui.status_bar( [ui.status_item("feat/deployments", icon="git-branch"), ui.status_item("0", icon="circle-x"), ui.status_item("2", icon="triangle-alert", icon_color=self.resolved_theme().warning)], [ui.status_item(f"Ln {line}, Col {col}"), ui.status_item("UTF-8"), ui.status_item("Python 3.13"), ui.status_item(None, icon="sparkles", tooltip="AI agent")]), left_width=250, right_width=220, bottom_height=172, ) if __name__ == "__main__": pz.run(FileExplorer) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/file_explorer")` and adapt it for you. ### Onboarding wizard > A multi-step onboarding wizard in Python: a vertical stepper, profile form, template cards, team invites, a summary and validation between steps. Source: https://albakore.github.io/pyzed/blocks/onboarding/ A four-step setup wizard: a vertical stepper with progress, a profile form, a workspace name with selectable template cards, team invites and a summary before launching. Each step validates before moving on. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/onboarding.py ``` #### What it shows - The current step is plain state (`self.step`); each step is a method that returns its view. - Selectable cards built from `pz.container(...).style(...).on(click=...)`. - The stepper is drawn with icons, `pz.rule` and theme tokens, so it follows any theme. - Back / Continue / Skip buttons and an error callout. #### Components used | Component | Used for | | --- | --- | | [Progress](https://albakore.github.io/pyzed/library/progress/) | Overall progress | | [Input](https://albakore.github.io/pyzed/library/input/) | Text fields | | [Select](https://albakore.github.io/pyzed/library/select/) | Role | | [Badge](https://albakore.github.io/pyzed/library/badge/) | Step number | | [Card](https://albakore.github.io/pyzed/library/card/) | Invites and summary | | [List item](https://albakore.github.io/pyzed/library/list-item/) | Invited people | | [Empty state](https://albakore.github.io/pyzed/library/empty-state/) | No invites yet | | [Callout](https://albakore.github.io/pyzed/library/callout/) | Validation errors | | [Button](https://albakore.github.io/pyzed/library/button/) | Navigation | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/onboarding.py" """Onboarding block: a multi-step setup wizard with a stepper, choice cards, validation and a summary.""" import pyzed as pz from pyzed import ui STEPS = [ ("user", "Your profile", "Tell us who you are"), ("layout-template", "Workspace", "Pick a template"), ("users", "Invite your team", "Work together from day one"), ("rocket", "All set", "Review and launch"), ] TEMPLATES = [ ("layout-dashboard", "Dashboard", "KPIs, charts and tables for internal tools."), ("bot", "AI assistant", "A streaming chat app wired to any LLM."), ("gamepad-2", "Game", "A canvas, a game loop and keyboard input."), ("code", "Developer tool", "A code editor with docks and a terminal."), ] ROLES = ["Developer", "Designer", "Data scientist", "Student", "Founder"] class Onboarding(pz.App): title = "Welcome" size = (1200, 760) def __init__(self): self.step = 1 self.name = "Ada Lovelace" self.role = "Developer" self.template = "AI assistant" self.workspace = "analytical-engine" self.invites = ["charles@babbage.dev"] self.invite = "" self.error = "" # -- actions --------------------------------------------------------------------------- def next(self): if self.step == 0 and not self.name.strip(): self.error = "Please enter your name." return if self.step == 1 and not self.workspace.strip(): self.error = "Your workspace needs a name." return self.error = "" self.step = min(self.step + 1, len(STEPS) - 1) def back(self): self.error, self.step = "", max(self.step - 1, 0) def add_invite(self): if "@" in self.invite and self.invite not in self.invites: self.invites.append(self.invite.strip()) self.invite = "" # -- pieces ---------------------------------------------------------------------------- def stepper(self): items = [] for i, (icon, title, subtitle) in enumerate(STEPS): done, current = i < self.step, i == self.step marker = pz.container( pz.icon("check" if done else icon, size=16, color="#ffffff" if done or current else None), width=36, height=36, center=True, ).style(background="accent" if done or current else "element", radius=18, border=(2, "accent" if current else "border_variant")) items.append(pz.row([ marker, pz.column([ui.label(title, weight="semibold" if current else None, color="text" if done or current else "text_muted"), ui.muted(subtitle, size="sm")], spacing=2), ], spacing=12, align="center")) if i < len(STEPS) - 1: items.append(pz.container(pz.rule(vertical=True, thickness=2), height=28, padding=(0, 0, 0, 17))) return pz.column([ pz.row([pz.container(pz.icon("zap", size=16, color="#ffffff"), padding=6).style( background="accent", radius=8), ui.label("Acme", size="lg", weight="semibold")], spacing=10, align="center"), pz.space(height=24), pz.column(items, spacing=4), pz.vspace(), ui.muted("Step {} of {}".format(self.step + 1, len(STEPS)), size="sm"), ui.progress(self.step + 1, max=len(STEPS), height=4), ], spacing=8, width=290, height="fill", padding=32).style(background="panel") def template_card(self, icon, title, text): selected = title == self.template return pz.container( pz.column([ pz.row([pz.container(pz.icon(icon, size=20), padding=8).style( background="accent" if selected else "element", radius=10, color="#ffffff" if selected else "text"), pz.hspace(), pz.icon("circle-check", size=18) if selected else None], align="start"), ui.label(title, weight="semibold"), ui.muted(text, size="sm"), ], spacing=10), padding=16, width="fill", height=150, ).style(background="surface", radius=12, border=(2 if selected else 1, "accent" if selected else "border"), ).on(click=lambda: setattr(self, "template", title), cursor="pointer") def profile_step(self): return pz.column([ ui.field("Full name", pz.container(ui.input(self.name, lambda v: setattr(self, "name", v)), width=340)), ui.field("What do you do?", ui.select(ROLES, self.role, lambda v: setattr(self, "role", v), width=340)), ], spacing=16) def workspace_step(self): cards = [self.template_card(*t) for t in TEMPLATES] return pz.column([ pz.column([ui.label("Workspace name", size="sm", weight="medium"), ui.input(self.workspace, lambda v: setattr(self, "workspace", v), icon="at-sign", size="lg")], spacing=6), pz.column([ui.label("Start from a template", size="sm", weight="medium"), pz.row(cards[:2], spacing=14), pz.row(cards[2:], spacing=14)], spacing=10), ], spacing=22) def team_step(self): people = [ui.list_item(email, None, icon="mail", end=ui.icon_button( "x", lambda email=email: self.invites.remove(email), size="sm")) for email in self.invites] return pz.column([ pz.row([ui.input(self.invite, lambda v: setattr(self, "invite", v), placeholder="teammate@company.com", icon="mail", on_submit=self.add_invite, size="lg"), ui.button("Add", self.add_invite, size="lg")], spacing=8), ui.card(pz.column(people, spacing=2) if people else ui.empty_state("No invites yet", icon="users"), padding=6), ], spacing=14) def summary_step(self): rows = [("Name", self.name), ("Role", self.role), ("Workspace", f"@{self.workspace}"), ("Template", self.template), ("Invites", str(len(self.invites)))] return ui.card(pz.column([pz.row([ui.muted(k), pz.hspace(), ui.label(v, weight="medium")]) for k, v in rows], spacing=12)) def view(self): icon, title, subtitle = STEPS[self.step] body = [self.profile_step, self.workspace_step, self.team_step, self.summary_step][self.step]() last = self.step == len(STEPS) - 1 content = pz.column([ pz.column([ui.badge(f"Step {self.step + 1}", tint="accent"), ui.heading(["Nice to meet you", "Create your workspace", "Bring your team", "You're ready to go"][self.step]), ui.muted(["We'll tailor Acme to how you work.", "A workspace holds your projects, people and settings. You can change it later.", "Invite people now or skip this step; they'll get an email.", "Here's what we'll set up for you."][self.step])], spacing=8), body, ui.callout(self.error, tint="error", icon="circle-alert") if self.error else None, pz.vspace(), pz.row([ ui.button("Back", self.back, icon="arrow-left", variant="ghost", disabled=self.step == 0), pz.hspace(), ui.button("Skip", self.next, variant="ghost") if self.step == 2 else None, ui.button("Launch workspace" if last else "Continue", self.next, variant="solid", size="lg", icon="rocket" if last else "arrow-right", icon_position="start" if last else "end"), ], spacing=8, align="center"), ], spacing=26, max_width=640, height="fill", padding=(48, 56)) return pz.row([self.stepper(), ui.divider(vertical=True), pz.container(content, width="fill", height="fill", align_x="center")], height="fill").style(background="background") if __name__ == "__main__": pz.run(Onboarding) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/onboarding")` and adapt it for you. ### Music player > A music player UI in Python: library sidebar, album header with gradient art, a track list with an animated equalizer and a player bar with live progress and volume. Source: https://albakore.github.io/pyzed/blocks/music-player/ A media player: a library sidebar with playlists, an album header with gradient cover art and a soft shadow, a track list where the playing song shows an animated equalizer, and a player bar with shuffle, previous, play / pause, next, repeat, a seek bar and volume. The timer is a real subscription, so the progress moves while it plays. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/music_player.py ``` #### What it shows - `pz.every` drives the playback clock only while playing (`subscriptions()`). - Cover art from `pz.Gradient`, a header that fades out with `pz.Paint(..., mask=pz.Mask([1, 0]))`. - An equalizer made of four small containers whose heights follow the clock. - Rows are `pz.button`s with hover and selected backgrounds from the theme. #### Components used | Component | Used for | | --- | --- | | [Slider](https://albakore.github.io/pyzed/library/slider/) | Seek bar and volume | | [Icon button](https://albakore.github.io/pyzed/library/icon-button/) | Player controls | | [List item](https://albakore.github.io/pyzed/library/list-item/) | Library and playlists | | [List header](https://albakore.github.io/pyzed/library/list-header/) | Playlists title | | [Button](https://albakore.github.io/pyzed/library/button/) | Play / pause | | [Scrollable](https://albakore.github.io/pyzed/library/scrollable/) | Album page | | [Effects](https://albakore.github.io/pyzed/library/effects/) | Gradients and shadows | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/music_player.py" """Music player block: a library sidebar, an album page with a track list and a player bar with live progress.""" import math import pyzed as pz from pyzed import ui COVER = pz.Gradient([(0.0, "#f97316"), (0.5, "#db2777"), (1.0, "#4f46e5")], angle=135) PLAYLISTS = [("Synthwave nights", "#8b5cf6"), ("Deep focus", "#14b8a6"), ("Morning run", "#f59e0b"), ("Lo-fi beats", "#ec4899"), ("Jazz classics", "#3b82f6")] TRACKS = [ ("Neon Skyline", "The Midnight Arcade", 214), ("Afterglow", "The Midnight Arcade", 187), ("Chrome Hearts", "The Midnight Arcade feat. Vela", 242), ("Pixel Rain", "The Midnight Arcade", 201), ("Overdrive", "The Midnight Arcade", 229), ("Last Arcade", "The Midnight Arcade", 263), ("Slow Fade", "The Midnight Arcade", 198), ] def clock(seconds): return f"{int(seconds) // 60}:{int(seconds) % 60:02d}" class MusicPlayer(pz.App): title = "Music" size = (1200, 760) def __init__(self): self.current = 2 self.elapsed = 96.0 self.playing = True self.volume = 0.7 self.shuffle, self.repeat = False, True self.liked = {0, 2, 4} def subscriptions(self): return [pz.every(0.25, self.tick, key="clock")] if self.playing else [] # -- actions --------------------------------------------------------------------------- def tick(self): self.elapsed += 0.25 if self.elapsed >= TRACKS[self.current][2]: self.skip(1) def play(self, index): self.current, self.elapsed, self.playing = index, 0.0, True def skip(self, step): self.play((self.current + step) % len(TRACKS)) def toggle_like(self, index): self.liked ^= {index} # -- pieces ---------------------------------------------------------------------------- def sidebar(self): lists = [ui.list_item(name, None, height=34, start=pz.container(width=22, height=22).style( background=pz.Gradient([color, "#111827"], angle=135), radius=5)) for name, color in PLAYLISTS] return pz.column([ pz.column([ui.list_item("Home", None, icon="house", height=32), ui.list_item("Search", None, icon="search", height=32), ui.list_item("Library", None, icon="library", selected=True, height=32)], spacing=2), ui.list_header("Playlists", end=ui.icon_button("plus", None, size="sm")), pz.column(lists, spacing=2), ], spacing=12, width=230, height="fill", padding=(16, 10)).style(background="panel") def equalizer(self): """Animated bars next to the playing track (they move with the position).""" bars = [] for i in range(4): level = 0.35 + 0.65 * abs(math.sin(self.elapsed * 3.1 + i * 1.7)) if self.playing else 0.3 bars.append(pz.container(width=3, height=4 + 10 * level).style(background="accent", radius=1)) return pz.container(pz.row(bars, spacing=2, align="end"), width=24, height=16, align_y="end") def header(self): return pz.row([ pz.container(pz.icon("disc-3", size=72, color="#ffffffcc"), width=190, height=190, center=True).style( background=COVER, radius=14, shadow=("#00000066", (0, 12), 30)), pz.column([ ui.label("ALBUM", size="xs", weight="semibold", color="text_muted"), pz.text("Midnight Arcade", size=44, weight="bold"), ui.muted("The Midnight Arcade · 2026 · 7 songs, 25 min"), pz.space(height=8), pz.row([ ui.button("Play" if not self.playing else "Pause", lambda: setattr(self, "playing", not self.playing), icon="pause" if self.playing else "play", variant="solid", size="lg"), ui.icon_button("shuffle", lambda: setattr(self, "shuffle", not self.shuffle), selected=self.shuffle, tooltip="Shuffle"), ui.icon_button("heart", None, tooltip="Save to library"), ui.icon_button("ellipsis", None), ], spacing=8, align="center"), ], spacing=6), ], spacing=28, align="end") def tracks(self): rows = [] for i, (title, artist, length) in enumerate(TRACKS): playing = i == self.current number = self.equalizer() if playing else ui.muted(str(i + 1)) rows.append(pz.button( pz.row([ pz.container(number, width=28), pz.column([ui.label(title, weight="medium", color="accent" if playing else "text"), ui.muted(artist, size="sm")], spacing=1, width="fill"), ui.icon_button("heart", lambda i=i: self.toggle_like(i), size="sm", color=self.resolved_theme().accent if i in self.liked else None), pz.container(ui.muted(clock(length)), width=50, align_x="end"), ], spacing=12, align="center"), lambda i=i: self.play(i), width="fill", padding=(6, 12), radius=8, bg=self.resolved_theme().ghost_selected if playing else "#00000000", hover_bg=self.resolved_theme().ghost_hover)) return pz.column(rows, spacing=2) def player(self): title, artist, length = TRACKS[self.current] now = pz.row([ pz.container(pz.icon("disc-3", size=22, color="#ffffffcc"), width=48, height=48, center=True).style( background=COVER, radius=8), pz.column([ui.label(title, weight="semibold"), ui.muted(artist, size="sm")], spacing=2), ui.icon_button("heart", lambda: self.toggle_like(self.current), size="sm", color=self.resolved_theme().accent if self.current in self.liked else None), ], spacing=12, align="center", width=300) controls = pz.column([ pz.row([ ui.icon_button("shuffle", lambda: setattr(self, "shuffle", not self.shuffle), selected=self.shuffle), ui.icon_button("skip-back", lambda: self.skip(-1)), pz.button(pz.icon("pause" if self.playing else "play", size=18, color="#111111"), lambda: setattr(self, "playing", not self.playing), width=40, height=40, padding=11, radius=20, bg="#ffffff", hover_bg="#e5e5e5"), ui.icon_button("skip-forward", lambda: self.skip(1)), ui.icon_button("repeat", lambda: setattr(self, "repeat", not self.repeat), selected=self.repeat), ], spacing=14, align="center"), pz.row([ ui.muted(clock(self.elapsed), size="xs"), ui.slider(self.elapsed, lambda v: setattr(self, "position", v), min=0, max=length, step=1, width=340), ui.muted(clock(length), size="xs"), ], spacing=10, align="center"), ], spacing=6, align="center", width="fill") volume = pz.row([ ui.icon_button("list-music", None, size="sm"), ui.icon_button("volume-2" if self.volume else "volume-x", lambda: setattr(self, "volume", 0 if self.volume else 0.7), size="sm"), ui.slider(self.volume, lambda v: setattr(self, "volume", v), width=110), ], spacing=6, align="center", width=300) return pz.container(pz.row([now, controls, pz.row([pz.hspace(), volume])], align="center"), padding=(12, 20), width="fill").style(background="title_bar") def view(self): main = pz.scrollable(pz.column([ pz.container(self.header(), padding=32, width="fill").style( background=pz.Paint(COVER, opacity=0.28, mask=pz.Mask([1, 0]))), pz.container(self.tracks(), padding=(0, 24, 24, 24)), ], spacing=8), height="fill") return pz.column([ pz.row([self.sidebar(), ui.divider(vertical=True), pz.container(main, width="fill", height="fill").style( background="editor")], height="fill"), ui.divider(), self.player(), ], height="fill") if __name__ == "__main__": pz.run(MusicPlayer) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/music_player")` and adapt it for you. ### Kanban board > A kanban board in Python: columns with counters, cards with tags, priorities, due dates, checklists and assignees, filters and moving cards between columns. Source: https://albakore.github.io/pyzed/blocks/kanban/ A sprint board: four columns with counters, cards with colored tags, priority arrows, due dates (today in red), comment counts, checklist progress and assignees, a text filter, an *only mine* toggle, and buttons to add cards and move the selected one between columns. The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark. #### Run it From the repository root: ```bash python examples/blocks/kanban.py ``` #### What it shows - Cards are dataclasses; moving one is changing its `column` field. - The selected card gets an accent border and its move buttons. - Each column scrolls on its own inside a `pz.scrollable`. - A soft shadow on the cards with `.style(shadow=...)`. #### Components used | Component | Used for | | --- | --- | | [Badge](https://albakore.github.io/pyzed/library/badge/) | Tags | | [Count badge](https://albakore.github.io/pyzed/library/count-badge/) | Column counters | | [Progress](https://albakore.github.io/pyzed/library/progress/) | Checklists | | [Facepile](https://albakore.github.io/pyzed/library/facepile/) | Assignees | | [Breadcrumbs](https://albakore.github.io/pyzed/library/breadcrumbs/) | Location | | [Search input](https://albakore.github.io/pyzed/library/search-input/) | Filter | | [Icon button](https://albakore.github.io/pyzed/library/icon-button/) | Add and move | | [Scrollable](https://albakore.github.io/pyzed/library/scrollable/) | Columns | | [Button](https://albakore.github.io/pyzed/library/button/) | Only mine, new task | #### Code The whole block, ready to copy. It only needs `pip install pyzed-ui`. ```python title="examples/blocks/kanban.py" """Kanban block: a project board with columns, labelled cards, assignees, progress and moving cards along.""" from dataclasses import dataclass, field from typing import List import pyzed as pz from pyzed import ui COLUMNS = [("backlog", "Backlog", "circle-dashed", "text_muted"), ("todo", "To do", "circle", "info"), ("doing", "In progress", "circle-dot", "warning"), ("done", "Done", "circle-check", "success")] TAGS = {"design": "accent", "backend": "info", "bug": "error", "docs": "success", "ai": "warning"} PRIORITY = {"high": ("chevrons-up", "error"), "medium": ("chevron-up", "warning"), "low": ("chevron-down", "info")} @dataclass class Card: id: int title: str column: str tags: List[str] = field(default_factory=list) people: List[str] = field(default_factory=list) priority: str = "medium" due: str = "" comments: int = 0 checklist: tuple = (0, 0) CARDS = [ Card(1, "Research onboarding drop-off", "backlog", ["design"], ["ML"], "low", "Nov 12", 2), Card(2, "Dark mode for the billing page", "backlog", ["design"], [], "low"), Card(3, "Rate limit the public API", "backlog", ["backend"], ["JL"], "medium", "Nov 20"), Card(4, "Streaming answers in the support bot", "todo", ["ai", "backend"], ["KV", "IN"], "high", "Nov 3", 5, (1, 4)), Card(5, "Write the MCP setup guide", "todo", ["docs", "ai"], ["SD"], "medium", "Nov 6", 1), Card(6, "Crash when exporting large CSVs", "doing", ["bug"], ["JL"], "high", "Today", 8, (2, 3)), Card(7, "New pricing table", "doing", ["design"], ["ML", "WK"], "medium", "Oct 30", 3, (3, 5)), Card(8, "Migrate jobs to SQLModel", "doing", ["backend"], ["KV"], "medium", "Nov 1", 0, (4, 6)), Card(9, "Landing page hero", "done", ["design"], ["ML"], "medium", "", 6, (4, 4)), Card(10, "Fix login redirect loop", "done", ["bug"], ["IN"], "high", "", 2, (2, 2)), ] class Kanban(pz.App): title = "Board" size = (1200, 760) def __init__(self): self.cards = [Card(**vars(c)) for c in CARDS] self.query = "" self.mine = False self.selected = 6 def move(self, card, step): keys = [c[0] for c in COLUMNS] card.column = keys[max(0, min(len(keys) - 1, keys.index(card.column) + step))] self.selected = card.id def add(self, column): new_id = max(c.id for c in self.cards) + 1 self.cards.append(Card(new_id, "Untitled task", column, [], ["KV"], "low")) self.selected = new_id # -- view ------------------------------------------------------------------------------ def card(self, card): t = self.resolved_theme() icon, tint = PRIORITY[card.priority] done, total = card.checklist meta = [] if card.due: meta.append(pz.row([pz.icon("calendar", size=13, color=t.error if card.due == "Today" else t.icon_muted), ui.label(card.due, size="xs", color="error" if card.due == "Today" else "text_muted")], spacing=4, align="center")) if card.comments: meta.append(pz.row([pz.icon("message-square", size=13, color=t.icon_muted), ui.muted(str(card.comments), size="xs")], spacing=4, align="center")) if total: meta.append(pz.row([pz.icon("list-checks", size=13, color=t.success if done == total else t.icon_muted), ui.muted(f"{done}/{total}", size="xs")], spacing=4, align="center")) selected = card.id == self.selected body = pz.column([ pz.row([*[ui.badge(tag, tint=TAGS[tag]) for tag in card.tags], pz.hspace(), pz.icon(icon, size=14, color=t.color(tint))], spacing=6, align="center"), ui.label(card.title, weight="medium"), ui.progress(done, max=total, height=3, tint="success" if done == total else "accent") if total else None, pz.row([*meta, pz.hspace(), ui.facepile(card.people, size=22) if card.people else None], spacing=12, align="center"), pz.row([ui.icon_button("arrow-left", lambda: self.move(card, -1), size="xs", tooltip="Move left"), pz.hspace(), ui.icon_button("arrow-right", lambda: self.move(card, 1), size="xs", tooltip="Move right")]) if selected else None, ], spacing=10) return pz.container(body, padding=12, width="fill").style( background="elevated", radius=10, border=(1.5 if selected else 1, "accent" if selected else "border_variant"), shadow=("#0000001f", (0, 2), 6), ).on(click=lambda: setattr(self, "selected", card.id), cursor="pointer") def column(self, key, title, icon, tint): q = self.query.lower() cards = [c for c in self.cards if c.column == key and q in c.title.lower() and (not self.mine or "KV" in c.people)] header = pz.row([ pz.icon(icon, size=15, color=self.resolved_theme().color(tint)), ui.label(title, weight="semibold"), ui.count_badge(len(cards), tint="accent" if key == "doing" else "info"), pz.hspace(), ui.icon_button("plus", lambda: self.add(key), size="sm", tooltip="Add a card"), ui.icon_button("ellipsis", None, size="sm"), ], spacing=8, align="center") return pz.container( pz.column([header, pz.scrollable(pz.column([self.card(c) for c in cards], spacing=10), height="fill")], spacing=12, height="fill"), padding=12, width="fill", height="fill", ).style(background="panel", radius=12) def view(self): top = pz.row([ pz.column([ ui.breadcrumbs([("folder", "Acme"), "Product", "Sprint 42"]), pz.row([ui.heading("Sprint 42", level=2), ui.badge("Oct 21 – Nov 3", tint="info", icon="calendar")], spacing=10, align="center"), ], spacing=6), pz.hspace(), pz.container(ui.search_input(self.query, lambda v: setattr(self, "query", v), placeholder="Filter cards…"), width=220), ui.button("Only mine", lambda: setattr(self, "mine", not self.mine), icon="user", selected=self.mine, variant="ghost"), ui.facepile(["ML", "JL", "IN", "SD", "KV", "WK"], size=28, max_shown=5), ui.button("New task", lambda: self.add("todo"), icon="plus", variant="solid"), ], spacing=10, align="center") board = pz.row([self.column(*c) for c in COLUMNS], spacing=14, height="fill") return pz.column([top, board], spacing=20, padding=24, height="fill").style(background="background") if __name__ == "__main__": pz.run(Kanban) ``` #### Make it yours Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/kanban")` and adapt it for you.