more styling

This commit is contained in:
John Lancaster
2026-07-30 00:41:26 -05:00
parent a18c8456d3
commit 226f19b2c6
3 changed files with 221 additions and 11 deletions
+2 -1
View File
@@ -3,7 +3,7 @@ name: nicegui
description: 'Reference hub for NiceGUI and FastAPI application structure, typed configuration, ASGI and Uvicorn startup, UI composition, styling, bindable state, interactions, troubleshooting, testing, and source documentation. Use when planning, implementing, reviewing, deploying, or debugging NiceGUI applications; load only the references relevant to the task.' description: 'Reference hub for NiceGUI and FastAPI application structure, typed configuration, ASGI and Uvicorn startup, UI composition, styling, bindable state, interactions, troubleshooting, testing, and source documentation. Use when planning, implementing, reviewing, deploying, or debugging NiceGUI applications; load only the references relevant to the task.'
x-personal-mcp: x-personal-mcp:
id: nicegui id: nicegui
version: 2.4.0 version: 2.5.0
tags: tags:
- nicegui - nicegui
- fastapi - fastapi
@@ -76,6 +76,7 @@ Load [architecture and styling](./references/architecture-and-styling.md) for:
- component extraction decisions - component extraction decisions
- Quasar props, Tailwind utilities, and custom CSS boundaries - Quasar props, Tailwind utilities, and custom CSS boundaries
- responsive layout and static asset conventions - responsive layout and static asset conventions
- Tailwind and Quasar breakpoint scales, container queries, and responsive testing
- uniformly scaling dialogs on mobile - uniformly scaling dialogs on mobile
- preserving Quasar field proportions - preserving Quasar field proportions
- keeping detached `QSelect` menus anchored - keeping detached `QSelect` menus anchored
@@ -90,6 +90,67 @@ Quasar coordinates field height, padding, labels, values, icons, and floating-la
## Responsive Layout ## Responsive Layout
Treat a breakpoint as the width where the content needs a different composition, not as a device label. Professional responsive systems use as few breakpoints as the content requires and let layout interpolate between them.
Use this decision order:
1. Prefer intrinsic Grid or Flex behavior when the browser can adapt continuously without a breakpoint.
2. Use Tailwind viewport variants for ordinary page-shell changes.
3. Use CSS container queries when a reusable component should respond to its allocated width rather than the browser width.
4. Use Quasar visibility classes when coordinating with Quasar drawers, headers, tables, and other layout behavior.
5. Use Quasar's reactive Screen plugin only when JavaScript behavior or component props must change.
6. Send viewport state to Python only when the server truly needs it; resizing should not normally create a client-server event stream.
### Start With Intrinsic Layout
The cleanest breakpoint is often no breakpoint. Use wrapping, minimum sizes, and fluid tracks before adding width conditions:
```python
with ui.element('section').classes(
'grid w-full gap-4 '
'[grid-template-columns:repeat(auto-fit,minmax(min(18rem,100%),1fr))]'
):
for item in items:
item_card(item)
```
Other high-value levers are:
- `flex-wrap` for toolbars and groups whose children have natural widths
- `min-w-0` for flex or grid children that must be allowed to shrink
- `minmax()` and `auto-fit` for fluid card grids
- `max-w-*` plus `mx-auto` for readable page shells
- `clamp()` for bounded fluid spacing or type, not unbounded viewport scaling
- `overflow-x-auto` for genuinely tabular data that cannot collapse without losing meaning
Add a breakpoint only when the intrinsic layout produces a specific failure such as an unreadable line length, clipped control, awkward empty space, or an action wrapping away from its context.
### Know Which Breakpoint Scale You Are Using
NiceGUI exposes both Tailwind utilities and Quasar components. Their default breakpoint names do not mean the same widths.
| Name | Tailwind minimum width | Quasar half-open range |
| --- | ---: | ---: |
| `xs` | Not defined by default | 0 to <600px |
| `sm` | 640px | 600 to <1024px |
| `md` | 768px | 1024 to <1440px |
| `lg` | 1024px | 1440 to <1920px |
| `xl` | 1280px | 1920px and wider |
| `2xl` | 1536px | Not defined |
Tailwind's default values are defined in `rem`; the pixel equivalents above assume the usual 16px initial font size. Tailwind variants are mobile-first minimum-width rules. Unprefixed classes apply everywhere; `sm:*` starts at 40rem and continues upward. Quasar defines exclusive named bands and `lt.*` or `gt.*` tests around thresholds at 600, 1024, 1440, and 1920px.
This creates real disagreement zones. At 620px, for example, Quasar reports `sm`, while Tailwind's `sm:*` rules have not started. At 1100px, Quasar reports `md`, while Tailwind's `lg:*` rules are active. Do not compare breakpoint names across the two systems as if they were shared tokens.
Choose one owner for each responsive decision:
- Use Tailwind consistently for a page's spacing, grid, width, and flex changes.
- Use Quasar's scale for Quasar component behavior and visibility tied to its layout system.
- If one element must coordinate across both systems, use explicit width values in shared CSS or configure and verify a unified project scale.
- Avoid custom breakpoints until repeated content failures justify a new token. Keep custom Tailwind breakpoints in the same unit, normally `rem`, so generated rules sort predictably.
### Use Tailwind Mobile-First Variants
Use Tailwind breakpoint classes for ordinary page adaptation: Use Tailwind breakpoint classes for ordinary page adaptation:
```python ```python
@@ -104,6 +165,127 @@ with ui.row().classes("w-full gap-4 flex-wrap sm:flex-nowrap"):
- Keep controls and primary actions visible without horizontal scrolling. - Keep controls and primary actions visible without horizontal scrolling.
- Test the longest realistic labels, values, errors, and menu options. - Test the longest realistic labels, values, errors, and menu options.
Think of `sm:` as "from 640px upward," not "on phones." When a style should apply only within one band, combine minimum and maximum variants such as `md:max-lg:*`. Arbitrary variants such as `max-[700px]:*` are appropriate for a proven one-off content threshold, but repeated arbitrary values are a signal to define a named project token or shared CSS rule.
### Use Container Queries For Reusable Components
Viewport breakpoints answer "how wide is the browser?" Container queries answer "how much room did this component receive?" The latter is usually correct for a card, filter panel, inspector, or reusable toolbar that can appear in a full-width page, drawer, split pane, or dialog.
Use native CSS when the project's Tailwind or UnoCSS runtime does not guarantee container-query variants:
```python
with ui.element('section').classes('result-panel'):
with ui.element('article').classes('result-card'):
result_summary()
result_actions()
```
```css
.result-panel {
container: results / inline-size;
}
.result-card {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@container results (width >= 36rem) {
.result-card {
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
}
}
```
Use `container-type: inline-size` or the `container: <name> / inline-size` shorthand for normal horizontal adaptation. Name containers when nesting makes the nearest query container ambiguous. Keep a usable one-column or wrapped layout as the fallback.
If the enabled utility engine supports Tailwind container variants, the equivalent shape is concise:
```python
with ui.element('section').classes('@container'):
with ui.element('article').classes(
'flex flex-col gap-4 @min-[36rem]:flex-row'
):
result_summary()
result_actions()
```
NiceGUI can run Tailwind or an UnoCSS preset, and compatibility is not identical. Verify container-query and arbitrary-variant support against the selected engine before standardizing on utility syntax; native CSS remains the portable baseline.
### Use Quasar Visibility And Screen State Deliberately
For pure show/hide behavior tied to Quasar's scale, prefer Quasar responsive CSS classes over JavaScript. Typical classes include `xs`, `sm`, `md`, `lg`, `xl`, `lt-sm`, `lt-md`, `lt-lg`, `lt-xl`, `gt-xs`, `gt-sm`, `gt-md`, and `gt-lg`.
```python
ui.button('Menu', icon='menu').props('flat').classes('lt-md')
ui.row().classes('gt-sm').props('aria-label="Primary navigation"')
```
Do not render two interactive versions of a control unless both copies have correct labels, focus behavior, state synchronization, and accessibility semantics. Prefer changing layout around one control when possible.
Use the Quasar Screen plugin when a component prop must react rather than merely its CSS. NiceGUI passes props beginning with `:` as client-side JavaScript expressions:
```python
ui.table(columns=columns, rows=rows).props(':dense="$q.screen.lt.md"')
```
Quasar exposes `$q.screen.width`, `height`, `name`, named-band booleans, and `lt`/`gt` flags. Its own guidance prefers responsive CSS classes when possible for performance. Changing Screen plugin sizes at runtime does not change CSS breakpoints, so avoid calling `setSizes()` unless the project also updates and verifies the corresponding stylesheet tokens.
Body classes such as `screen--sm` are opt-in Quasar configuration and can delay first meaningful paint. They are rarely needed in NiceGUI when media queries, responsive utility classes, or direct `$q.screen` expressions already solve the problem.
### Width Is Not The Only Lever
Screen width is a poor proxy for interaction capability. A wide touchscreen can still have a coarse pointer, while a narrow desktop window can have hover and a mouse. Use media features for the capability being adapted:
```css
.icon-action {
min-width: 2.75rem;
min-height: 2.75rem;
}
@media (hover: hover) and (pointer: fine) {
.icon-action:hover {
background: var(--action-hover-background);
}
}
@media (pointer: coarse) {
.icon-action {
min-width: 3rem;
min-height: 3rem;
}
}
@media (prefers-reduced-motion: reduce) {
.app-shell *,
.app-shell *::before,
.app-shell *::after {
scroll-behavior: auto;
transition-duration: 0.01ms;
animation-duration: 0.01ms;
animation-iteration-count: 1;
}
}
```
Use `hover` for hover affordances, `pointer` for target sizing, `prefers-reduced-motion` for non-essential movement, `orientation` only when orientation itself changes the usable composition, and height queries for short viewports such as landscape phones or split-screen windows.
### What Experienced Teams Standardize
Mature implementations usually standardize the decision process more than the number of device presets:
1. A small, documented viewport scale for application shells.
2. Component-owned container thresholds for portable reusable UI.
3. Design tokens for page max widths, gutters, minimum control sizes, and readable line lengths.
4. Mobile-first base styles with progressive enhancement at wider sizes.
5. CSS-driven adaptation; JavaScript only when behavior or data requirements change.
6. Content-driven breakpoint selection based on observed failure points.
7. A viewport test matrix that includes exact boundaries and one pixel on each side.
Do not maintain Python booleans such as `is_mobile` from user-agent strings. Do not hardcode separate phone, tablet, and desktop component trees when CSS can reflow one semantic tree. Device categories age quickly; content constraints and input capabilities are durable.
## Static Assets And Shared CSS ## Static Assets And Shared CSS
- Mount static assets from the composition layer. - Mount static assets from the composition layer.
@@ -222,17 +404,18 @@ Use `popup-content-class=app-item-detail-menu` to target the detached menu and e
The card's pre-zoom maximum height must account for the scale: The card's pre-zoom maximum height must account for the scale:
$$ \[
ext{pre-zoom max height} \begin{aligned}
= h_{\mathrm{pre}} &= \frac{h_{\mathrm{visible}}}{s} \\
\frac{\text{desired visible height}}{\text{scale}} \text{where } s &= \text{the zoom scale}
$$ \end{aligned}
\]
For a desired visual height of `90dvh` at $1.2\times$: For a desired visual height of `90dvh` at \(1.2\times\):
$$ \[
90 / 1.2 = 75 \frac{90\,\mathrm{dvh}}{1.2} = 75\,\mathrm{dvh}
$$ \]
Therefore: Therefore:
@@ -268,6 +451,21 @@ Confirm custom breakpoint values against the target application's Quasar configu
Test the complete page at representative mobile and desktop viewports. For a dialog, include a mobile viewport such as $390 \times 844$. Test the complete page at representative mobile and desktop viewports. For a dialog, include a mobile viewport such as $390 \times 844$.
For every breakpoint actually used by the page, test immediately below, exactly at, and immediately above it. For a 640px threshold, that means widths of 639, 640, and 641px. This catches inclusive/exclusive mistakes and exposes Tailwind/Quasar disagreement. Do not multiply this into every possible height; pair boundary widths with the heights that stress the feature.
Use a compact matrix that covers:
1. The narrowest supported viewport, not only a current flagship phone.
2. A typical portrait phone and a short landscape phone.
3. Every active content breakpoint at $b - 1$, $b$, and $b + 1$.
4. A common laptop width with browser sidebars or devtools reducing available space.
5. A wide desktop to verify maximum widths and avoid stretched content.
6. A narrow component inside a wide viewport when container queries are used.
7. Keyboard navigation and 200% browser zoom.
8. Coarse-pointer, hover-capable, and reduced-motion emulation where those queries exist.
Prefer assertions about invariants over screenshot-only approval: no horizontal document overflow, no overlap, minimum target sizes remain usable, the intended number of grid columns is present, drawers and actions have the expected visibility, and focus order remains logical. Add screenshots for visual regression, but keep geometry and accessibility checks for failures a pixel diff cannot explain.
- Page sections do not overlap or introduce unintended horizontal scrolling. - Page sections do not overlap or introduce unintended horizontal scrolling.
- Responsive rows wrap or resize as designed. - Responsive rows wrap or resize as designed.
- Dialog remains inside the viewport. - Dialog remains inside the viewport.
@@ -301,7 +499,14 @@ Do not alter otherwise correct component styling solely to accommodate this auto
- [Quasar components](https://quasar.dev/vue-components) - [Quasar components](https://quasar.dev/vue-components)
- [Quasar field](https://quasar.dev/vue-components/field/) - [Quasar field](https://quasar.dev/vue-components/field/)
- [Quasar select](https://quasar.dev/vue-components/select/) - [Quasar select](https://quasar.dev/vue-components/select/)
- [Quasar breakpoints](https://quasar.dev/style/breakpoints/) - [Quasar Screen plugin documentation source](https://github.com/quasarframework/quasar/blob/dev/docs/src/pages/options/screen-plugin.md)
- [Quasar Screen plugin implementation](https://github.com/quasarframework/quasar/blob/dev/ui/src/plugins/screen/Screen.js)
- [Tailwind responsive design](https://tailwindcss.com/docs/responsive-design) - [Tailwind responsive design](https://tailwindcss.com/docs/responsive-design)
- [MDN media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries)
- [MDN container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries)
- [web.dev content-driven breakpoints](https://web.dev/learn/design/media-queries#choose_breakpoints_based_on_the_content)
- [MDN hover capability](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover)
- [MDN pointer accuracy](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer)
- [MDN reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)
- [MDN `zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom) - [MDN `zoom`](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom)
- [Playwright input actions](https://playwright.dev/docs/input) - [Playwright input actions](https://playwright.dev/docs/input)
@@ -40,7 +40,11 @@ Use these links to verify framework-specific behavior before relying on version-
!!! info "Styling sources" !!! info "Styling sources"
- [Tailwind utility-first styling](https://tailwindcss.com/docs/utility-first) - [Tailwind utility-first styling](https://tailwindcss.com/docs/utility-first)
- [Tailwind responsive design and container queries](https://tailwindcss.com/docs/responsive-design)
- [Quasar components](https://quasar.dev/vue-components) - [Quasar components](https://quasar.dev/vue-components)
- [Quasar Screen plugin documentation source](https://github.com/quasarframework/quasar/blob/dev/docs/src/pages/options/screen-plugin.md)
- [CSS media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries)
- [CSS container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries)
## Persistence ## Persistence