nicegui component

This commit is contained in:
John Lancaster
2026-08-07 23:48:08 -05:00
parent f240486a7e
commit b5d6e60d45
8 changed files with 558 additions and 302 deletions
@@ -41,10 +41,11 @@ If the selected component or source layout cannot be identified unambiguously, a
## Required References ## Required References
Apply both references before implementation: Apply these references before implementation:
1. Component boundaries, responsive layout, Quasar props, Tailwind utilities, and shared CSS: [NiceGUI Page Layout and Styling](../../skills/nicegui/references/architecture-and-styling.md) 1. Package boundaries, dependency direction, and page or component ownership: [NiceGUI Application Architecture](../../skills/nicegui/references/architecture.md)
2. Typed UI state, propagation, mutable defaults, binding strictness, and version checks: [Binding Dataclasses Deep Dive](../../skills/nicegui/references/binding-dataclasses.md) 2. Responsive layout, Quasar props, Tailwind utilities, and shared CSS: [NiceGUI Styling and Customization](../../skills/nicegui/references/styling-and-customization.md)
3. Typed UI state, propagation, mutable defaults, binding strictness, and version checks: [Binding Dataclasses Deep Dive](../../skills/nicegui/references/binding-dataclasses.md)
## Workflow ## Workflow
+22 -9
View File
@@ -48,13 +48,13 @@ Load [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) for:
- exposing programmatic startup through `[project.scripts]` - exposing programmatic startup through `[project.scripts]`
- reload, worker, and process-local state constraints - reload, worker, and process-local state constraints
### Components And Styling ### Styling And Customization
Load [architecture and styling](./references/architecture-and-styling.md) for: Load [styling and customization](./references/styling-and-customization.md) for:
- page, component, and service boundaries - progressive discovery through NiceGUI docs, constructors, and Quasar docs
- component extraction decisions - Quasar props, slots, events, and NiceGUI customization methods
- Quasar props, Tailwind utilities, and custom CSS boundaries - Tailwind for structural styling and static stylesheets for fine tuning
- responsive layout and static asset conventions - responsive layout and static asset conventions
- Tailwind and Quasar breakpoint scales, container queries, and responsive testing - Tailwind and Quasar breakpoint scales, container queries, and responsive testing
- uniformly scaling dialogs on mobile - uniformly scaling dialogs on mobile
@@ -63,6 +63,15 @@ Load [architecture and styling](./references/architecture-and-styling.md) for:
- sizing scrollable dialog cards under CSS `zoom` - sizing scrollable dialog cards under CSS `zoom`
- validating zoomed controls with Playwright or a browser - validating zoomed controls with Playwright or a browser
### Special Component Customization
Load [special component customization](./references/special-component-customization.md) for:
- the required source-research gate before generating component customizations
- `ui.select` constructors, Quasar props, slots, detached popups, and option caveats
- `ui.icon` names, icon families, sizing, colors, assets, and Material Symbol variants
- component-specific accessibility, sanitization, and validation checks
### Bindable State ### Bindable State
Load [bindable dataclasses](./references/binding-dataclasses.md) for: Load [bindable dataclasses](./references/binding-dataclasses.md) for:
@@ -103,12 +112,14 @@ Load [source documentation](./references/source-documentation.md) when:
1. Load [application architecture](./references/architecture.md). 1. Load [application architecture](./references/architecture.md).
2. Add [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) when FastAPI owns the application or startup must be exposed as a project command. 2. Add [FastAPI and Uvicorn startup](./references/fastapi-uvicorn-startup.md) when FastAPI owns the application or startup must be exposed as a project command.
3. Add [architecture and styling](./references/architecture-and-styling.md) only when page and component design is in scope. 3. Add [styling and customization](./references/styling-and-customization.md) only when page layout or visual customization is in scope.
### Page Or Component Work ### Page Or Component Work
1. Load [architecture and styling](./references/architecture-and-styling.md). 1. Load [application architecture](./references/architecture.md) for page and component ownership decisions.
2. Add [interaction patterns](./references/interaction-patterns.md) or [bindable dataclasses](./references/binding-dataclasses.md) according to the page behavior. 2. Load [styling and customization](./references/styling-and-customization.md) for layout, responsive behavior, or visual customization.
3. Add [special component customization](./references/special-component-customization.md) when the work targets `ui.select`, `ui.icon`, or another component with specialized Quasar behavior.
4. Add [interaction patterns](./references/interaction-patterns.md) or [bindable dataclasses](./references/binding-dataclasses.md) according to the page behavior.
### Debugging Or Production Review ### Debugging Or Production Review
@@ -122,7 +133,9 @@ Load [source documentation](./references/source-documentation.md) when:
- Keep business logic out of UI components and event handlers. - Keep business logic out of UI components and event handlers.
- Avoid blocking I/O and CPU-heavy work in the UI event loop. - Avoid blocking I/O and CPU-heavy work in the UI event loop.
- Prefer event-driven updates and explicit refreshes over unrelated polling. - Prefer event-driven updates and explicit refreshes over unrelated polling.
- Prefer Tailwind utilities, then Quasar props, then reusable component helpers; use minimal shared CSS when those are insufficient. - Discover component capabilities through NiceGUI docs and constructors, then the wrapped Quasar API.
- Research the current NiceGUI and Quasar source documentation before generating component-specific code or CSS.
- Prefer constructor arguments and native Quasar features through NiceGUI; use Tailwind for structure and scoped static CSS for stable fine tuning.
- Provide loading, success, and failure states for user-triggered work. - Provide loading, success, and failure states for user-triggered work.
- Treat version-specific guidance as a prompt to verify the project's dependency version. - Treat version-specific guidance as a prompt to verify the project's dependency version.
@@ -1,289 +0,0 @@
# NiceGUI Page Layout And Styling
Use this reference to structure NiceGUI pages, choose component boundaries, apply responsive layout, and introduce custom CSS without fighting Quasar's internal geometry.
## Ownership And Dependency Boundaries
Keep dependencies flowing in one direction:
- pages import components and services
- components contain presentation logic only
- services contain business logic and do not import UI
- bootstrap code mounts static assets and loads shared CSS once
Suggested module split:
```text
src/my_app/
ui/
pages/
components/
static/
services/
api/
```
Page modules should compose a route from reusable presentation and service calls. They should not own domain rules, persistence, or long-running synchronous work.
## Page Composition
Build the outer layout before styling individual controls:
1. Define the page shell and width constraints.
2. Establish responsive rows, columns, gaps, and wrapping.
3. Add semantic sections and repeated components.
4. Configure Quasar component appearance with props.
5. Add custom CSS only for behavior that props and utilities cannot express safely.
```python
with ui.column().classes("w-full max-w-6xl mx-auto gap-6 px-4"):
page_header(title="Inventory")
with ui.row().classes("w-full gap-4 flex-wrap lg:flex-nowrap items-start"):
filters_panel().classes("w-full lg:w-72 shrink-0")
item_grid().classes("w-full flex-1 min-w-0")
```
Use stable width, minimum-width, and flex constraints so labels, icons, validation messages, and loaded content do not shift the surrounding layout.
## Component Extraction
Extract a presentation pattern to `ui/components/` when it appears on two or more pages or when it owns a meaningful interaction boundary. Keep one-off route layout in the page module.
```python
def card_section(title: str, content: str) -> ui.card:
with ui.card().classes("w-full max-w-md") as card:
ui.label(title).classes("text-lg font-bold")
ui.label(content).classes("text-gray-600")
return card
```
Reusable components should accept data and event callbacks rather than import page state or business services implicitly.
## Styling Decision Order
NiceGUI wraps Quasar components. Choose the styling mechanism according to what it owns:
1. Use Quasar props for component appearance, density, labels, and popup behavior.
2. Use NiceGUI `.classes()` and Tailwind utilities for width, spacing, alignment, and responsive layout.
3. Use reusable component functions for repeated visual patterns.
4. Use `.style()` for genuinely dynamic inline values.
5. Use minimal shared CSS only when props and utilities are insufficient.
Common Quasar props include:
- `outlined`
- `dense`
- `stack-label`
- `popup-content-class`
- `input-class`
- `input-style`
Avoid overriding internal selectors such as:
- `.q-field__label`
- `.q-field__native`
- `.q-field__control`
- `.q-field__input`
Quasar coordinates field height, padding, labels, values, icons, and floating-label transforms. Changing only one internal part tends to cause clipping or overlap.
## Responsive Layout
Support these layouts only:
- mobile: a single-column layout with wrapping toolbars and full-width controls
- landscape desktop: $1920 \times 1080$ with side-by-side panels where they improve scanning
- portrait desktop: $1080 \times 1920 with stacked panels or a narrow fixed sidebar
Build the mobile layout first, then add one desktop breakpoint when a row or grid needs more space. Prefer flex wrapping and fluid grids before adding another breakpoint. Use Tailwind classes for page layout and Quasar props for component behavior.
```python
with ui.row().classes('w-full flex-wrap gap-4 lg:flex-nowrap items-start'):
filters_panel().classes('w-full lg:w-72 shrink-0')
item_grid().classes('w-full flex-1 min-w-0')
```
Use `min-w-0` for flexible children, `flex-wrap` for toolbars, and `max-w-* mx-auto` to keep portrait layouts readable. Do not add device-specific component trees, container queries, or custom breakpoints unless a supported layout demonstrates a concrete failure.
## Static Assets And Shared CSS
- Mount static assets from the composition layer.
- Load shared CSS once rather than injecting it from individual pages.
- Keep custom CSS tokenized with variables and scoped to application classes.
- Avoid broad rules against Quasar internals.
- Verify mount paths, reverse-proxy rewrites, and cache behavior.
```python
from pathlib import Path
from fastapi.staticfiles import StaticFiles
STATIC_DIR = Path(__file__).parent / "ui" / "static"
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
ui.add_css((STATIC_DIR / "css" / "base.css").read_text(encoding="utf-8"))
```
## Responsive Dialog Pattern
Use whole-card scaling when a form dialog must become uniformly larger on mobile while preserving Quasar's internal proportions. Keep detached select menus unscaled and make the card itself scrollable.
### Use Normal Field Density
Normal Quasar fields are approximately `56px` high, while dense fields are approximately `40px` high. Remove `dense` when larger controls are needed.
```python
ui.input("Name").props("outlined")
ui.number("Quantity").props("outlined")
ui.select(...).props(
"outlined popup-content-class=app-item-detail-menu"
)
ui.textarea("Description").props("outlined autogrow")
```
Add a scoped class to the dialog card:
```python
ui.card().classes("app-detail-card app-item-detail-card")
```
### Scale The Complete Card
```css
:root {
--item-dialog-scale: 1;
--item-dialog-max-height: calc(100dvh - 3rem);
}
.app-item-detail-card {
width: min(50rem, 50vw);
max-height: var(--item-dialog-max-height);
overflow-y: auto;
overscroll-behavior: contain;
zoom: var(--item-dialog-scale);
}
/* Restore Quasar's baseline if a global rule overrides it. */
.app-item-detail-card .q-field,
.app-item-detail-menu {
font-size: 14px;
}
@media (max-width: 599px) {
:root {
--item-dialog-scale: 1.2;
/* 75dvh becomes 90dvh after 1.2x zoom. */
--item-dialog-max-height: 75dvh;
}
.app-item-detail-card {
width: 80vw;
}
.app-item-detail-menu {
font-size: 16.8px;
}
}
```
The main mobile tuning knob is:
```css
--item-dialog-scale: 1.2;
```
### Keep Detached Popups Unscaled
Do not apply `zoom` or `transform: scale()` to a `QSelect` popup menu. Quasar renders menus outside the dialog and positions them from the unscaled anchor geometry. Scaling the menu container afterward separates it from its field.
Avoid:
```css
.app-item-detail-card,
.app-item-detail-menu {
zoom: 1.2;
}
```
Use:
```css
.app-item-detail-card {
zoom: 1.2;
}
.app-item-detail-menu {
font-size: 16.8px;
}
```
Use `popup-content-class=app-item-detail-menu` to target the detached menu and enlarge its text without changing its coordinate system.
### Account For Zoom When Scrolling
The card's pre-zoom maximum height must account for the scale:
\[
\begin{aligned}
h_{\mathrm{pre}} &= \frac{h_{\mathrm{visible}}}{s} \\
\text{where } s &= \text{the zoom scale}
\end{aligned}
\]
For a desired visual height of `90dvh` at \(1.2\times\):
\[
\frac{90\,\mathrm{dvh}}{1.2} = 75\,\mathrm{dvh}
\]
Therefore:
```css
--item-dialog-max-height: 75dvh;
```
Apply scrolling to the card itself:
```css
.app-item-detail-card {
max-height: var(--item-dialog-max-height);
overflow-y: auto;
overscroll-behavior: contain;
}
```
This keeps the dimmed page stationary while the form scrolls.
### Match The Quasar Breakpoint
Quasar's extra-small breakpoint ends at `599.98px`. A mobile-only rule can use:
```css
@media (max-width: 599px) {
/* Mobile rules. */
}
```
Confirm custom breakpoint values against the target application's Quasar configuration.
## Validation Checklist
Check each completed page at these three viewports:
1. A representative mobile viewport, such as $390 \times 844$.
2. Landscape desktop at $1920 \times 1080$.
3. Portrait desktop at $1080 \times 1920$.
Confirm that page sections do not overlap, toolbars wrap on mobile, desktop panels use the available space without becoming excessively wide, and dialogs remain visible and scroll to their final field.
## Sources
!!! info "Primary sources"
- [NiceGUI element styling and props](https://nicegui.io/documentation/element)
- [NiceGUI binding properties](https://nicegui.io/documentation/section_binding_properties)
- [Quasar components](https://quasar.dev/vue-components)
- [Quasar field](https://quasar.dev/vue-components/field/)
- [Quasar select](https://quasar.dev/vue-components/select/)
- [Tailwind responsive design](https://tailwindcss.com/docs/responsive-design)
- [MDN `zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom)
@@ -23,7 +23,6 @@ Recommended base shape:
│ └─ app/ │ └─ app/
│ ├─ __init__.py │ ├─ __init__.py
│ ├─ main.py │ ├─ main.py
│ ├─ bootstrap.py
│ ├─ config.py │ ├─ config.py
│ ├─ logging.py │ ├─ logging.py
│ ├─ api/ │ ├─ api/
@@ -69,6 +68,14 @@ Prefer:
Avoid imports from services back into API or UI modules. Avoid imports from services back into API or UI modules.
## Page And Component Ownership
Page modules compose routes from presentation components and service calls. They should not own domain rules, persistence, or long-running synchronous work.
Extract a presentation pattern to `ui/components/` when it appears on two or more pages or owns a meaningful interaction boundary. Keep one-off route composition in the page module. Reusable components should accept data and event callbacks instead of importing page state or business services implicitly.
For page composition, responsive layout, Quasar props, and CSS customization, load [styling and customization](./styling-and-customization.md).
## Optional Persistence ## Optional Persistence
Use only when the product requires durable data. Use only when the product requires durable data.
@@ -5,6 +5,9 @@ Use these links to verify framework-specific behavior before relying on version-
## NiceGUI ## NiceGUI
!!! info "NiceGUI sources" !!! info "NiceGUI sources"
- [Component documentation](https://nicegui.io/documentation)
- [Element styling, props, and events](https://nicegui.io/documentation/element)
- [NiceGUI element source](https://github.com/zauberzeug/nicegui/tree/main/nicegui/elements)
- [Pages, routing, and FastAPI integration](https://www.nicegui.io/documentation/section_pages_routing) - [Pages, routing, and FastAPI integration](https://www.nicegui.io/documentation/section_pages_routing)
- [`ui.run_with` implementation](https://github.com/zauberzeug/nicegui/blob/main/nicegui/ui_run_with.py) - [`ui.run_with` implementation](https://github.com/zauberzeug/nicegui/blob/main/nicegui/ui_run_with.py)
- [FastAPI integration example](https://github.com/zauberzeug/nicegui/blob/main/examples/fastapi/main.py) - [FastAPI integration example](https://github.com/zauberzeug/nicegui/blob/main/examples/fastapi/main.py)
@@ -0,0 +1,144 @@
# NiceGUI Special Component Customization
Use this reference for components whose NiceGUI wrapper, Quasar implementation, popup behavior, slots, or external assets require component-specific handling. Start with [styling and customization](./styling-and-customization.md) for the general escalation workflow.
## Source Research Gate
Research the target component before generating code or CSS. Do not rely on a remembered NiceGUI or Quasar API.
For each component:
1. Read its current NiceGUI documentation page.
2. Inspect the constructor and implementation in the target project's installed NiceGUI package.
3. Confirm the wrapped Quasar component in the NiceGUI source.
4. Read the matching Quasar guide and API definition for props, slots, events, and methods.
5. Check the target project's pinned NiceGUI version before using current upstream behavior.
6. Record which layer owns each proposed customization before writing it.
Use current upstream source only as a fallback when the target environment is unavailable. If installed and upstream behavior differ, follow the installed version and state the difference.
## `ui.select`
### Source Map
- [NiceGUI `ui.select` documentation](https://nicegui.io/documentation/select)
- [NiceGUI `Select` source](https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/select.py)
- [Quasar `QSelect` guide](https://quasar.dev/vue-components/select/)
- [Quasar `QSelect` API source](https://github.com/quasarframework/quasar/blob/dev/ui/src/components/select/QSelect.json)
NiceGUI's `Select` wraps Quasar `QSelect` but owns important Python-side behavior. Its constructor handles options, labels, values, change callbacks, input filtering, new-value modes, multiple selection, clearing, validation, and key generation. Use those constructor parameters before adding equivalent Quasar props manually.
### Customization Order
1. Use `options`, `label`, `value`, `on_change`, `with_input`, `new_value_mode`, `multiple`, `clearable`, `validation`, and `key_generator` through the NiceGUI constructor.
2. Use `.props()` for additional documented `QSelect` behavior such as field design, chips, option density, popup classes, popup positioning, or menu/dialog behavior.
3. Use `.classes()` and Tailwind for the field's structural width and placement.
4. Use named slots for prepend, append, loading, no-option, selected, or option content when props are insufficient.
5. Use `popup-content-class` to attach an application class to the detached options popup, then fine-tune it in a static stylesheet.
```python
from nicegui import ui
item_select = ui.select(
options={"chair": "Chair", "desk": "Desk", "lamp": "Lamp"},
label="Items",
multiple=True,
clearable=True,
with_input=True,
).props(
"outlined use-chips options-dense "
"popup-content-class=app-item-select-menu"
).classes(
"w-full md:max-w-md"
)
with item_select.add_slot("prepend"):
ui.icon("inventory_2")
```
```css
.app-item-select-menu {
max-height: min(24rem, 60dvh);
}
```
### Select-Specific Caveats
- NiceGUI accepts a list of values or a dictionary mapping values to labels. Do not assume the Python options model is the same as Quasar's JavaScript object-array examples.
- After mutating `options`, call `update()` or use `set_options()` so the client receives the change.
- `new_value_mode` enables input automatically. For dictionary options with `add`, NiceGUI requires a `key_generator`.
- A multiple select has a list value. NiceGUI normalizes a non-list initial value, but application state should still use the intended list shape.
- `map-options` has a Quasar performance cost. Do not add it to NiceGUI's mapped options without confirming that the wrapper's value translation requires it.
- `display-value-html` and `options-html` can create cross-site scripting risk. When using `selected`, `selected-item`, or `option` slots, the application owns sanitization.
- Custom option slots use virtual scrolling. When one option renders multiple sibling elements, Quasar requires `q-virtual-scroll--with-prev` on every additional sibling.
- Buttons placed in `before`, `after`, `prepend`, or `append` field slots do not propagate clicks to the parent. A submit button in one of those slots needs its own submit handler.
- `QSelect` renders its popup outside the field. Style it through `popup-content-class`; do not assume a descendant selector beneath the field will reach it.
- Quasar switches between menu and dialog popup behavior by platform. Verify forced `behavior=menu` carefully on iOS when input filtering is enabled.
Use `.on()` or `run_method()` only after confirming the event or method in the installed Quasar API. Prefer NiceGUI's `on_change`, `set_options()`, value bindings, and `is_showing_popup` when they cover the behavior.
## `ui.icon`
### Source Map
- [NiceGUI `ui.icon` documentation](https://nicegui.io/documentation/icon)
- [NiceGUI `Icon` source](https://github.com/zauberzeug/nicegui/blob/main/nicegui/elements/icon.py)
- [Quasar `QIcon` guide](https://quasar.dev/vue-components/icon/)
- [Quasar `QIcon` API source](https://github.com/quasarframework/quasar/blob/dev/ui/src/components/icon/QIcon.json)
- [Google Material Symbols and Icons](https://fonts.google.com/icons)
NiceGUI's `Icon` is a thin `QIcon` wrapper. Its constructor exposes `name`, `size`, and `color`; the source forwards these to a `q-icon` element. Use Quasar's icon naming and asset rules for anything beyond those parameters.
### Customization Order
1. Choose an icon family that is actually loaded by the application.
2. Pass the documented icon name, size, and color to `ui.icon()`.
3. Use `.props()` for supported `QIcon` props such as `left`, `right`, or a custom render tag.
4. Use `.classes()` for structural placement and an application class for stable visual variants.
5. Use a static stylesheet for Material Symbol axes, state variants, custom webfonts, or repeated effects.
```python
from nicegui import ui
ui.icon(
"sym_o_home",
size="1.5rem",
color="primary",
).classes(
"app-symbol-filled shrink-0"
).tooltip(
"Home"
)
```
```css
.app-symbol-filled {
font-variation-settings:
"FILL" 1,
"wght" 400,
"GRAD" 0,
"opsz" 24;
}
```
### Icon-Specific Caveats
- Material icon names use snake case. Material variants use prefixes such as `o_`, `r_`, `s_`, `sym_o_`, `sym_r_`, and `sym_s_`.
- Other icon families have their own prefixes and require their webfont or stylesheet to be loaded. A valid name does not load the corresponding asset.
- `size` accepts CSS units or Quasar sizes such as `xs`, `sm`, `md`, `lg`, and `xl`. Quasar implements icon sizing through `font-size`.
- Icon color inherits text color unless the `color` prop or a CSS color overrides it.
- Material Symbol variable axes apply to webfont icons, not static SVG icon exports.
- Quasar also supports SVG path strings, `svguse:` references, and `img:` URLs. Confirm the exact `QIcon` name format and mount path before generating one of these forms.
- For an action, use a semantic control such as `ui.button(icon=..., on_click=...)` and give it an accessible label or tooltip. Do not turn a bare decorative icon into an unlabeled control.
- Prefer `ui.icon(...).tooltip(...)` over manually constructing tooltip slot markup when NiceGUI's method covers the requirement.
## Completion Check
Before accepting a special-component customization:
1. Cite the NiceGUI component page and implementation that were inspected.
2. Cite the matching Quasar guide or API source.
3. Identify constructor arguments, Quasar props, slots, Tailwind classes, and stylesheet rules separately.
4. Confirm detached popup or external asset behavior where applicable.
5. Test keyboard interaction, focus, labels, and tooltips.
6. Test the supported mobile, landscape desktop, and portrait desktop viewports.
@@ -0,0 +1,334 @@
# NiceGUI Styling And Customization
Use this reference to discover how a NiceGUI component can be customized, apply the least invasive supported mechanism, and introduce CSS without fighting Quasar's internal geometry.
For package boundaries, dependency direction, and page or component ownership, load [application architecture](./architecture.md).
## Progressive Customization Workflow
Increase the customization level only when the previous source does not expose what the design requires:
1. Read the NiceGUI documentation page for the component.
2. Inspect the NiceGUI element function or class constructor.
3. Identify the wrapped Quasar component and read its documentation.
4. Use Quasar props, slots, and events through NiceGUI's native customization APIs.
5. Use Tailwind classes for structural layout.
6. Add a scoped static stylesheet for stable visual fine tuning.
Stop as soon as the required behavior is supported. Do not begin by targeting Quasar's generated DOM or internal selectors.
### 1. Start With The NiceGUI Component Page
Find the component in the [NiceGUI documentation](https://nicegui.io/documentation). Check its examples, parameters, methods, events, bindings, and inheritance before writing CSS. The component page establishes the public NiceGUI API and often demonstrates the intended Quasar integration.
Confirm the target project's installed NiceGUI version because the current online documentation can differ from the pinned release.
### 2. Inspect The NiceGUI Constructor
Read the signature and implementation of the imported NiceGUI function or element class. The constructor reveals accepted Python parameters, defaults, event callbacks, validation, and values NiceGUI forwards to the frontend.
Use editor navigation or runtime inspection against the project's selected environment:
```python
from inspect import getsource, signature
from nicegui import ui
print(signature(ui.select))
print(getsource(ui.select))
```
When `ui.<name>` is a factory or alias, follow it to the element class in the [NiceGUI element sources](https://github.com/zauberzeug/nicegui/tree/main/nicegui/elements). Prefer the installed package source when behavior may differ by version.
### 3. Read The Underlying Quasar Component Docs
NiceGUI wraps Quasar components such as [`QInput`](https://quasar.dev/vue-components/input/), [`QSelect`](https://quasar.dev/vue-components/select/), and [`QDialog`](https://quasar.dev/vue-components/dialog/). Use the matching Quasar component page to discover its complete props, slots, events, methods, and behavior notes.
Map Quasar's Vue API onto the NiceGUI wrapper instead of copying a Vue template. Verify that a prop or slot exists in the Quasar version used by the installed NiceGUI release.
### 4. Apply Native Quasar Features Through NiceGUI
Use the NiceGUI element customization methods to reach the supported Quasar surface:
- `.props(...)` for Quasar properties and boolean flags
- `.classes(...)` for Tailwind utilities and stable application class names
- `.style(...)` for dynamic inline values or a quick, local probe
- `.on(...)` for events that are not represented by a constructor callback
- slots or child elements for Quasar extension points exposed by the wrapper
```python
with ui.select(
options=items,
label="Item",
).props(
"outlined clearable options-dense popup-content-class=app-item-menu"
).classes(
"w-full md:max-w-md"
) as item_select:
with item_select.add_slot("prepend"):
ui.icon("inventory_2")
```
Prefer constructor arguments when NiceGUI exposes the behavior directly. Use `.props()` for supported Quasar features that are not constructor parameters. Use slots when the Quasar docs define a semantic insertion point; do not reproduce that content with absolute positioning.
## Structural Styling With Tailwind
Use standard [Tailwind utility classes](https://tailwindcss.com/docs/utility-first) for page and component structure:
- display, flex, and grid behavior
- width, height, and maximum-width constraints
- spacing, gaps, padding, and alignment
- wrapping, overflow, and responsive variants
- typography and common visual utilities when they fully express the design
Build the outer layout before fine-tuning individual controls:
1. Define the page shell and width constraints.
2. Establish responsive rows, columns, gaps, and wrapping.
3. Add semantic sections and repeated visual patterns.
4. Configure component appearance and behavior with constructor arguments and Quasar props.
5. Add stable application classes for any remaining stylesheet rules.
```python
with ui.column().classes("w-full max-w-6xl mx-auto gap-6 px-4"):
page_header(title="Inventory")
with ui.row().classes("w-full gap-4 flex-wrap lg:flex-nowrap items-start"):
filters_panel().classes("w-full lg:w-72 shrink-0")
item_grid().classes("w-full flex-1 min-w-0")
```
Use stable width, minimum-width, and flex constraints so labels, icons, validation messages, and loaded content do not shift the surrounding layout.
## Fine Tuning With Static Stylesheets
Move stable fine tuning into a static stylesheet after the structure and native component configuration are correct. Static stylesheets provide reusable selectors, media queries, pseudo-classes, CSS variables, and a clear cascade that inline declarations cannot provide.
Attach an application-owned class with `.classes()` or a Quasar popup prop, then scope stylesheet rules beneath it:
```python
ui.select(...).props("popup-content-class=app-item-menu").classes(
"app-item-select w-full md:max-w-md"
)
```
```css
.app-item-select {
--app-field-accent: #176b5b;
}
.app-item-select:focus-within {
filter: drop-shadow(0 0 0.25rem rgb(23 107 91 / 20%));
}
.app-item-menu {
max-height: min(24rem, 60dvh);
}
```
Use `.style()` when a value is calculated at runtime or while testing a local hypothesis. Once a declaration becomes stable or repeated, move it to the stylesheet and keep only the application class in Python.
Avoid overriding Quasar internals such as `.q-field__label`, `.q-field__native`, `.q-field__control`, and `.q-field__input` unless the public props, slots, and application-level selectors cannot express the requirement.
Quasar coordinates field height, padding, labels, values, icons, and floating-label transforms. Changing only one internal part tends to cause clipping or overlap.
## Responsive Layout
Support these layouts only:
- mobile: a single-column layout with wrapping toolbars and full-width controls
- landscape desktop: $1920 \times 1080$ with side-by-side panels where they improve scanning
- portrait desktop: $1080 \times 1920$ with stacked panels or a narrow fixed sidebar
Build the mobile layout first, then add one desktop breakpoint when a row or grid needs more space. Prefer flex wrapping and fluid grids before adding another breakpoint. Use Tailwind classes for page layout and Quasar props for component behavior.
```python
with ui.row().classes("w-full flex-wrap gap-4 lg:flex-nowrap items-start"):
filters_panel().classes("w-full lg:w-72 shrink-0")
item_grid().classes("w-full flex-1 min-w-0")
```
Use `min-w-0` for flexible children, `flex-wrap` for toolbars, and `max-w-* mx-auto` to keep portrait layouts readable. Do not add device-specific component trees, container queries, or custom breakpoints unless a supported layout demonstrates a concrete failure.
## Loading Stylesheets And Static Assets
- Mount and link static stylesheets once from the composition layer rather than injecting CSS from individual pages.
- Keep custom CSS tokenized with variables and scoped to application classes.
- Avoid broad rules against Quasar internals.
- Mount referenced assets in the composition layer.
- Verify mount paths, reverse-proxy rewrites, and cache behavior.
```python
from pathlib import Path
from fastapi.staticfiles import StaticFiles
STATIC_DIR = Path(__file__).parent / "ui" / "static"
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
ui.add_head_html(
'<link rel="stylesheet" href="/static/css/base.css">',
shared=True,
)
```
## Worked Example: Responsive Dialog Customization
This example begins with normal field density and Quasar popup props, then uses an application class and static stylesheet for the remaining responsive fine tuning. Use whole-card scaling when a form dialog must become uniformly larger on mobile while preserving Quasar's internal proportions. Keep detached select menus unscaled and make the card itself scrollable.
### Use Normal Field Density
Normal Quasar fields are approximately `56px` high, while dense fields are approximately `40px` high. Remove `dense` when larger controls are needed.
```python
ui.input("Name").props("outlined")
ui.number("Quantity").props("outlined")
ui.select(...).props("outlined popup-content-class=app-item-detail-menu")
ui.textarea("Description").props("outlined autogrow")
```
Add a scoped class to the dialog card:
```python
ui.card().classes("app-detail-card app-item-detail-card")
```
### Scale The Complete Card
```css
:root {
--item-dialog-scale: 1;
--item-dialog-max-height: calc(100dvh - 3rem);
}
.app-item-detail-card {
width: min(50rem, 50vw);
max-height: var(--item-dialog-max-height);
overflow-y: auto;
overscroll-behavior: contain;
zoom: var(--item-dialog-scale);
}
/* Restore Quasar's baseline if a global rule overrides it. */
.app-item-detail-card .q-field,
.app-item-detail-menu {
font-size: 14px;
}
@media (max-width: 599px) {
:root {
--item-dialog-scale: 1.2;
/* 75dvh becomes 90dvh after 1.2x zoom. */
--item-dialog-max-height: 75dvh;
}
.app-item-detail-card {
width: 80vw;
}
.app-item-detail-menu {
font-size: 16.8px;
}
}
```
The main mobile tuning knob is:
```css
--item-dialog-scale: 1.2;
```
### Keep Detached Popups Unscaled
Do not apply `zoom` or `transform: scale()` to a `QSelect` popup menu. Quasar renders menus outside the dialog and positions them from the unscaled anchor geometry. Scaling the menu container afterward separates it from its field.
Avoid:
```css
.app-item-detail-card,
.app-item-detail-menu {
zoom: 1.2;
}
```
Use:
```css
.app-item-detail-card {
zoom: 1.2;
}
.app-item-detail-menu {
font-size: 16.8px;
}
```
Use `popup-content-class=app-item-detail-menu` to target the detached menu and enlarge its text without changing its coordinate system.
### Account For Zoom When Scrolling
The card's pre-zoom maximum height must account for the scale:
\[
\begin{aligned}
h_{\mathrm{pre}} &= \frac{h_{\mathrm{visible}}}{s} \\
\text{where } s &= \text{the zoom scale}
\end{aligned}
\]
For a desired visual height of `90dvh` at \(1.2\times\):
\[
\frac{90\,\mathrm{dvh}}{1.2} = 75\,\mathrm{dvh}
\]
Therefore:
```css
--item-dialog-max-height: 75dvh;
```
Apply scrolling to the card itself:
```css
.app-item-detail-card {
max-height: var(--item-dialog-max-height);
overflow-y: auto;
overscroll-behavior: contain;
}
```
This keeps the dimmed page stationary while the form scrolls.
### Match The Quasar Breakpoint
Quasar's extra-small breakpoint ends at `599.98px`. A mobile-only rule can use:
```css
@media (max-width: 599px) {
/* Mobile rules. */
}
```
Confirm custom breakpoint values against the target application's Quasar configuration.
## Validation Checklist
Check each completed page at these three viewports:
1. A representative mobile viewport, such as $390 \times 844$.
2. Landscape desktop at $1920 \times 1080$.
3. Portrait desktop at $1080 \times 1920$.
Confirm that page sections do not overlap, toolbars wrap on mobile, desktop panels use the available space without becoming excessively wide, and dialogs remain visible and scroll to their final field.
## Sources
!!! info "Primary sources"
- [NiceGUI element styling and props](https://nicegui.io/documentation/element)
- [NiceGUI binding properties](https://nicegui.io/documentation/section_binding_properties)
- [Quasar components](https://quasar.dev/vue-components)
- [Quasar field](https://quasar.dev/vue-components/field/)
- [Quasar select](https://quasar.dev/vue-components/select/)
- [Tailwind responsive design](https://tailwindcss.com/docs/responsive-design)
- [MDN `zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom)
+43
View File
@@ -27,10 +27,53 @@ def _register_components(mcp: FastMCP, registry: DocsRegistry) -> None:
return read_docs_markdown_path(registry, path) return read_docs_markdown_path(registry, path)
def _register_resource_tools(mcp: FastMCP) -> None:
@mcp.tool(
name="list_resources",
description=(
"List available MCP resources and URI templates. Use before read_resource to discover skill guidance."
),
annotations=_ro_annotations(),
)
async def list_resources() -> dict[str, list[dict[str, str | None]]]:
resources = await mcp.list_resources()
templates = await mcp.list_resource_templates()
return {
"resources": [
{
"uri": str(resource.uri),
"name": resource.name,
"description": resource.description,
"mime_type": resource.mime_type,
}
for resource in resources
],
"templates": [
{
"uri_template": template.uri_template,
"name": template.name,
"description": template.description,
"mime_type": template.mime_type,
}
for template in templates
],
}
@mcp.tool(
name="read_resource",
description="Read a resource URI returned by list_resources, including skill files, manifests, and references.",
annotations=_ro_annotations(),
)
async def read_resource(uri: str) -> dict[str, object]:
result = await mcp.read_resource(uri)
return result.model_dump(mode="json", exclude_none=True)
def create_mcp() -> FastMCP: def create_mcp() -> FastMCP:
registry = get_docs_registry() registry = get_docs_registry()
mcp = FastMCP("personal-mcp", on_duplicate="error") mcp = FastMCP("personal-mcp", on_duplicate="error")
_register_components(mcp, registry) _register_components(mcp, registry)
mcp.add_provider(create_prompts_provider()) mcp.add_provider(create_prompts_provider())
mcp.add_provider(create_skills_provider()) mcp.add_provider(create_skills_provider())
_register_resource_tools(mcp)
return mcp return mcp