css reference page

This commit is contained in:
John Lancaster
2026-07-29 23:47:24 -05:00
parent c9b6e137f2
commit efba051cb5
3 changed files with 278 additions and 43 deletions
+7 -2
View File
@@ -3,7 +3,7 @@ name: nicegui
description: 'Reference hub for NiceGUI and FastAPI application structure, UI composition, styling, bindable state, interactions, troubleshooting, testing, and source documentation. Use when planning, implementing, reviewing, or debugging NiceGUI applications; load only the references relevant to the task.'
x-personal-mcp:
id: nicegui
version: 2.0.0
version: 2.2.0
tags:
- nicegui
- fastapi
@@ -57,8 +57,13 @@ Load [architecture and styling](./references/architecture-and-styling.md) for:
- page, component, and service boundaries
- component extraction decisions
- Tailwind and Quasar styling order
- Quasar props, Tailwind utilities, and custom CSS boundaries
- responsive layout and static asset conventions
- uniformly scaling dialogs on mobile
- preserving Quasar field proportions
- keeping detached `QSelect` menus anchored
- sizing scrollable dialog cards under CSS `zoom`
- validating zoomed controls with Playwright or a browser
### Bindable State
@@ -1,31 +1,54 @@
# Architecture and Styling Reference
# NiceGUI Page Layout And Styling
## Project Boundaries
Use this reference to structure NiceGUI pages, choose component boundaries, apply responsive layout, and introduce custom CSS without fighting Quasar's internal geometry.
Use this dependency direction:
## 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
- static assets are mounted and loaded once at bootstrap
- bootstrap code mounts static assets and loads shared CSS once
Suggested module split:
```text
src/app/
ui/pages/
ui/components/
ui/static/
src/my_app/
ui/
pages/
components/
static/
services/
api/
bootstrap.py
```
## Component Extraction Rules
Page modules should compose a route from reusable presentation and service calls. They should not own domain rules, persistence, or long-running synchronous work.
Extract to ui/components when a pattern appears in two or more pages.
## Page Composition
Keep in-page if the layout is specific to a single route.
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:
@@ -35,43 +58,250 @@ def card_section(title: str, content: str) -> ui.card:
return card
```
## Tailwind-First Layout Pattern
Use Tailwind utility classes for structure and spacing.
Use breakpoint classes for responsive behavior.
Use .style() only for values that must be computed dynamically.
```python
with ui.column().classes("w-full"):
with ui.row().classes("w-full gap-4 flex-wrap sm:flex-nowrap"):
ui.card().classes("flex-1 min-w-64")
ui.card().classes("flex-1 min-w-64")
```
Reusable components should accept data and event callbacks rather than import page state or business services implicitly.
## Styling Decision Order
1. Tailwind utility classes
2. Quasar props
3. Reusable styled component functions
4. Minimal custom CSS loaded once at bootstrap (only when needed)
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
Use Tailwind breakpoint classes for ordinary page adaptation:
```python
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="src/app/static"), name="static")
ui.add_css(open("src/app/static/css/base.css").read())
with ui.row().classes("w-full gap-4 flex-wrap sm:flex-nowrap"):
ui.card().classes("w-full sm:flex-1 sm:min-w-64")
ui.card().classes("w-full sm:flex-1 sm:min-w-64")
```
## Static Asset Rules
- Start with a usable mobile layout, then add larger breakpoint behavior.
- Allow dense toolbars to wrap or collapse intentionally.
- Use `min-w-0` on flexible content that must shrink inside a row.
- Keep controls and primary actions visible without horizontal scrolling.
- Test the longest realistic labels, values, errors, and menu options.
- Keep custom CSS small and tokenized with variables.
- Avoid per-page CSS injection.
- Verify static mount paths and reverse proxy rewrites.
## Static Assets And Shared CSS
## Links
- 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:
$$
ext{pre-zoom max height}
=
\frac{\text{desired visible height}}{\text{scale}}
$$
For a desired visual height of `90dvh` at $1.2\times$:
$$
90 / 1.2 = 75
$$
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
Test the complete page at representative mobile and desktop viewports. For a dialog, include a mobile viewport such as $390 \times 844$.
- Page sections do not overlap or introduce unintended horizontal scrolling.
- Responsive rows wrap or resize as designed.
- Dialog remains inside the viewport.
- Dialog has `scrollHeight > clientHeight` when its content is taller than its maximum height.
- Scrolling reaches the final form field.
- Select menus open directly against their fields.
- Menus have no horizontal overflow.
- Values and floating labels are not clipped.
- Select arrows and other icons scale with the card.
- Detached popup menus report `zoom: 1`.
As a precision check, compare the menu edge with the field edge using browser geometry. One corrected implementation measured within approximately `0.5px` horizontally and `0.14px` vertically; treat those values as an example observation, not a framework guarantee.
## Playwright Caveat
Playwright locator clicks can calculate incorrect coordinates for elements inside CSS `zoom`. A failed locator click does not necessarily mean browser interaction is broken.
For verification, either:
- click using manually adjusted visual coordinates
- trigger the element through DOM evaluation
- test the interaction manually in a real browser
Do not alter otherwise correct component styling solely to accommodate this automation limitation.
## Sources
!!! info "Primary sources"
- [NiceGUI elements](https://nicegui.io/documentation/element)
- [NiceGUI element styling and props](https://nicegui.io/documentation/element)
- [NiceGUI binding properties](https://nicegui.io/documentation/section_binding_properties)
- [Tailwind utility-first styling](https://tailwindcss.com/docs/utility-first)
- [Quasar components](https://quasar.dev/vue-components)
- [Quasar field](https://quasar.dev/vue-components/field/)
- [Quasar select](https://quasar.dev/vue-components/select/)
- [Quasar breakpoints](https://quasar.dev/style/breakpoints/)
- [Tailwind responsive design](https://tailwindcss.com/docs/responsive-design)
- [MDN `zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom)
- [Playwright input actions](https://playwright.dev/docs/input)
+1 -1
View File
@@ -103,7 +103,7 @@ nav = [
{ "NiceGUI" = [
{ "Overview" = "skills/nicegui/SKILL.md" },
{ "App Architecture" = "skills/nicegui/references/architecture.md" },
{ "Style" = "skills/nicegui/references/architecture-and-styling.md" },
{ "Layout and Style" = "skills/nicegui/references/architecture-and-styling.md" },
{ "Binding" = "skills/nicegui/references/binding-dataclasses.md" },
{ "Flows" = "skills/nicegui/references/interaction-patterns.md" },
{ "Quality" = "skills/nicegui/references/troubleshooting-and-quality-gates.md" },