pythonista-nicegui
NiceGUI Development Best Practices
Core Philosophy
Understand the user workflow before building. Same data MUST look the same everywhere. Extract business logic from UI into testable controllers.
Key Points From References
- No threads for UI - Only use asyncio (see ui-architecture.md)
- Three-layer architecture: Data fetching → Controller → UI (see ui-architecture.md)
- Progressive complexity: Simple pages → Controllers → State machines (see ui-architecture.md)
Quick Start
# In Claude Code, use Grep tool:
# pattern="ui.card" path="src/"
# pattern="ui.dialog" path="src/"
# Check for existing components
ls src/*/ui/components/
Critical Styling Rules
# ALWAYS use inline style for gap (NiceGUI bug #2171)
with ui.row().classes("items-center").style("gap: 0.75rem"):
ui.icon("info")
ui.label("Message")
# NEVER use Tailwind gap-* classes - breaks on Ubuntu!
# Gap conversion: gap-2->0.5rem, gap-3->0.75rem, gap-4->1rem
# CORRECT: Explicit height for percentage children
with ui.card().style("height: 80vh"):
with ui.scroll_area().style("height: 100%"):
ui.label("Content")
# NEVER use height: 100% inside max-height container (collapses to 0!)
# Side-by-side with charts: use min-width: 0
with ui.element("div").style("display: flex; width: 100%; gap: 24px"):
with ui.element("div").style("flex: 1; min-width: 0"):
ui.highchart(options)
Controller Pattern
# Extract business logic to controller
class PageController:
async def handle_task_change(self, new_task: str) -> PageUpdate:
data = await self.fetcher.fetch_data(new_task)
return PageUpdate.refresh_all(data)
# UI layer is thin - delegates to controller
async def on_task_change(e):
logger.info(f"User selected task: {e.value}")
update = await controller.handle_task_change(e.value)
apply_ui_update(update)
# NEVER put business logic in UI handlers
Component Reuse
# BEFORE building any data display, answer:
# 1. Where else does this data type appear?
# 2. Should this be ONE component with modes?
# 3. Can user navigate from reference to source?
# Create reusable component with modes
class ItemReference:
def __init__(self, item, mode: Literal["LIBRARY", "REFERENCE", "PREVIEW"]):
if mode == "LIBRARY":
# Full card with edit actions
elif mode == "REFERENCE":
# Compact with navigation to source
Modal/Dialog Button Docking
# Primary actions MUST be always visible
with ui.dialog() as dialog, ui.card().style(
"height: 85vh; display: flex; flex-direction: column;"
):
with ui.scroll_area().style("flex: 1; overflow-y: auto;"):
# ... form content ...
# Sticky bottom action bar
with ui.element("div").style(
"position: sticky; bottom: 0; padding: 1rem;"
):
with ui.row().classes("justify-end"):
ui.button("Cancel", on_click=dialog.close)
ui.button("Save", on_click=save_handler)
Checklists
Styling Checklist
- No
gap-*Tailwind classes (use inline style) - No
height: 100%insidemax-heightcontainer - Side-by-side layouts use
min-width: 0on flex children - Modal buttons are docked (always visible)
Component Checklist
- Listed ALL locations where this data appears
- Designed component with modes for each context
- Same icon, typography, color coding everywhere
Architecture Checklist
- Business logic in controller, not UI handlers
- Data fetching returns Pydantic models
- All user actions logged at start of handlers
Reference Files
- references/nicegui-styling.md - Gap spacing, height issues, flexbox
- references/product-thinking.md - Component design, data display
- references/ui-architecture.md - Controllers, state management
Related Skills
- /pythonista-typing - Pydantic models for UI data
- /pythonista-testing - Testing controllers
- /pythonista-async - Async UI patterns
- /pythonista-patterning - Component reuse patterns
More from gigaverse-app/skillet
metaskill-authoring
Write Claude Code skills and SKILL.md files. Use when creating new skills, writing skill content, structuring SKILL.md, organizing skill directories, or when user mentions "write skill", "create skill", "author skill", "new skill", "skill structure", "SKILL.md", "skill content", "skill template".
9metaskill-triggering
Optimize skill triggers and descriptions for reliable activation. Use when skill is not triggering, optimizing trigger keywords, writing frontmatter, debugging activation, or when user mentions "trigger", "frontmatter", "description", "skill not triggering", "optimize trigger", "skill won't fire", "skill activation", "trigger keywords".
8metaskill-packaging
Package skills, agents, commands, and hooks as Claude Code plugins. Use when creating plugins, packaging skills for distribution, setting up plugin structure, dogfooding plugins, or when user mentions "plugin structure", "plugin.json", "package plugin", "distribute plugin", "marketplace", "dogfood", "install plugin", "plugin placement", "--plugin-dir".
8metaskill-naming
Brainstorm and validate names for plugins, skills, agents, and commands. Use when naming a new plugin, choosing atom names, validating naming conventions, or when user mentions "name plugin", "name skill", "naming convention", "brainstorm names", "what should I call", "plugin name", "good name for".
7metaskill-grouping
Create skill groups (multiple related skills packaged as a plugin). Use when creating plugins, organizing multiple related skills, building skill families, packaging tools together, or when user mentions "plugin", "multiple skills", "related skills", "skill group", "skill family", "organize skills", "cross-reference", "package skills", "shared agents". ALWAYS consider this pattern when someone asks to "create a skill" - they often need a skill GROUP packaged as a plugin.
7nicegui-development
Use when building UI with NiceGUI, creating components, fixing styling issues, or when user mentions "nicegui", "quasar", "tailwind", "ui.row", "ui.column", "gap spacing", "state management", "controller", "dialog", "modal", "ui component", "ui layout".
6