MITupdated 7mo ago
Understand the user workflow before building. Same data MUST look the same everywhere. Extract business logic from UI into testable controllers.
What can you do with Pythonista Nicegui?
name: pythonista-nicegui description: NiceGUI development best practices for Python web UIs. Use when building UI components, fixing layout issues, or working with NiceGUI/Quasar/Tailwind. Triggers on "nicegui", "quasar", "tailwind", "ui.row", "ui.column", "ui.card", "ui.dialog", "gap", "spacing", "layout", "modal", "component", "styling", "flexbox", "chart", or when creating/editing UI code.
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.
Critical Rules - Gap Spacing
NEVER use Tailwind gap-* classes in NiceGUI. ALWAYS use inline styles.
# CORRECT - inline style for gap
with ui.row().classes("items-center").style("gap: 0.75rem"):
ui.icon("info")
ui.label("Message")
# WRONG - breaks on Ubuntu (NiceGUI bug #2171)
with ui.row().classes("items-center gap-3"):
ui.icon("info")
ui.label("Message")
Gap Conversion Table
| Tailwind | Inline Style | Use Case |
|---|---|---|
gap-1 |
style("gap: 0.25rem") |
Minimal (icon + label) |
gap-2 |
style("gap: 0.5rem") |
Small (within groups) |
gap-3 |
style("gap: 0.75rem") |
Medium (between elements) |
gap-4 |
style("gap: 1rem") |
Large (between sections) |
gap-6 |
style("gap: 1.5rem") |
Extra large |
Critical Rules - Height Issues
NEVER use height: 100% inside a container with only max-height.
# WRONG - causes collapse to 0 height
with ui.card().style("max-height: 80vh"):
with ui.scroll_area().style("height: 100%"): # Collapses!
ui.label("Content")
# CORRECT - use explicit height
with ui.card().style("height: 80vh"):
with ui.scroll_area().style("height: 100%"):
ui.label("Content")
Critical Rules - Side-by-Side Layouts
Use min-width: 0 on flex children when using charts or wide content.
# WRONG - panels stack vertically
with ui.row().classes("w-full"):
with ui.column().classes("flex-1"):
ui.highchart(options) # Forces expansion
with ui.column().classes("flex-1"):
ui.highchart(options) # Wraps to next line
# CORRECT - explicit flexbox with 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) # Shrinks to fit
with ui.element("div").style("flex: 1; min-width: 0"):
ui.highchart(options) # Side by side
Modal/Dialog Button Docking
Primary action buttons MUST be always visible - dock to bottom.
with ui.dialog() as dialog, ui.card().style(
"height: 85vh; display: flex; flex-direction: column;"
):
# Scrollable content
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; "
"border-top: 1px solid var(--border-color);"
):
with ui.row().classes("justify-end").style("gap: 0.5rem"):
ui.button("Cancel", on_click=dialog.close)
ui.button("Save", on_click=save_handler)
Product Thinking - Component Design
Before building UI that displays data:
- Where else does this data type appear?
- Should this be ONE component with modes?
- Can user navigate from reference to source?
# CORRECT - Create reusable component with modes
class PromptReference:
def __init__(self, prompt, mode: Literal["LIBRARY", "REFERENCE", "PREVIEW"]):
if mode == "LIBRARY":
# Full card with edit actions
elif mode == "REFERENCE":
# Compact with navigation to source
# WRONG - Copy-paste UI code
# If same data in 2+ places, extract component
UI Architecture - Extract Controllers
Extract business logic from UI handlers into testable controllers.
# CORRECT - Controller handles business logic
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
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)
# WRONG - Business logic in UI handlers
async def on_task_change(e):
overview = await service.get_overview() # Fetching in UI!
if overview.tasks:
selected = overview.tasks[0] # Logic in UI!
chart.refresh(...) # All mixed together
Safe Quasar/Tailwind Classes
These work correctly - no inline style needed:
- Padding:
q-pa-md,q-pa-lg,q-pa-sm - Margin:
q-mb-md,q-mt-md - Width:
w-full,w-1/2,w-auto - Flexbox:
items-center,justify-between,flex-row - Colors:
bg-grey-9,text-positive - Typography:
text-h5,text-body1,font-bold
Requires inline style:
- Gap spacing:
gap-*->style("gap: Xrem")
Checklists
NiceGUI 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)
Data Display Component Checklist
- Listed ALL locations where this data appears
- Designed component with modes for each context
- User can navigate from reference to source
- Same icon, typography, color coding everywhere
UI Architecture Checklist
- Business logic in controller, not UI handlers
- Data fetching returns Pydantic models
- All user actions logged at start of handlers
- UI feedback when actions deferred/blocked
Reference Files
For detailed patterns:
- references/nicegui-styling.md - Gap, height, flexbox patterns
- references/product-thinking.md - Component design, data display
- references/ui-architecture.md - Controllers, state management
Related Skills
- For testing UI code, see
/pythonista-testing - For type safety, see
/pythonista-typing - For pattern discovery, see
/pythonista-patterning
Install
Add Pythonista Nicegui to your client. Pick the one you use.
npx skills add gigaverse-app/skilletInstalls every skill in the repository, then prompts for which to keep.
/plugin marketplace add gigaverse-app/skilletAdds the repository as a plugin marketplace; install individual plugins with `/plugin install`.
git clone https://github.com/gigaverse-app/skillet
cp -r pythonista-base/skills/pythonista-nicegui ~/.claude/skills/A skill is a plain directory. Copy it into `.claude/skills/` in a project or in your home directory.
Score
66 / 100
Good