focused styling
This commit is contained in:
@@ -11,7 +11,15 @@ window.MathJax = {
|
||||
}
|
||||
};
|
||||
|
||||
const mathJaxScript = document.createElement('script');
|
||||
mathJaxScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
|
||||
mathJaxScript.async = true;
|
||||
document.head.appendChild(mathJaxScript);
|
||||
document$.subscribe(() => {
|
||||
MathJax.startup.output.clearCache();
|
||||
MathJax.typesetClear();
|
||||
MathJax.texReset();
|
||||
MathJax.typesetPromise();
|
||||
});
|
||||
|
||||
component$.subscribe(({ ref }) => {
|
||||
if (ref.classList.contains('md-annotation')) {
|
||||
MathJax.typesetPromise([ref]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -90,201 +90,21 @@ Quasar coordinates field height, padding, labels, values, icons, and floating-la
|
||||
|
||||
## 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.
|
||||
Support these layouts only:
|
||||
|
||||
Use this decision order:
|
||||
- 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
|
||||
|
||||
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:
|
||||
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.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)
|
||||
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')
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```python
|
||||
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")
|
||||
```
|
||||
|
||||
- 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.
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -449,47 +269,13 @@ Confirm custom breakpoint values against the target application's Quasar configu
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Test the complete page at representative mobile and desktop viewports. For a dialog, include a mobile viewport such as $390 \times 844$.
|
||||
Check each completed page at these three viewports:
|
||||
|
||||
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.
|
||||
1. A representative mobile viewport, such as $390 \times 844$.
|
||||
2. Landscape desktop at $1920 \times 1080$.
|
||||
3. Portrait desktop at $1080 \times 1920$.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
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
|
||||
|
||||
@@ -499,14 +285,5 @@ Do not alter otherwise correct component styling solely to accommodate this auto
|
||||
- [Quasar components](https://quasar.dev/vue-components)
|
||||
- [Quasar field](https://quasar.dev/vue-components/field/)
|
||||
- [Quasar select](https://quasar.dev/vue-components/select/)
|
||||
- [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)
|
||||
- [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)
|
||||
- [Playwright input actions](https://playwright.dev/docs/input)
|
||||
Reference in New Issue
Block a user