nicegui component
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user