NiceGUI Page Structure, Typography, And Scaling¶
Use this reference for the physical structure of a NiceGUI page: container geometry, Tailwind layout classes, spacing, overflow, responsive reflow, font loading, typography, and scale. Prefer NiceGUI's Python mechanics or Tailwind classes wherever they can express the requirement; custom CSS is the fallback, not a parallel styling path. For the mechanics of how a NiceGUI Python element maps to a Quasar Vue component, including props, events, slots, methods, teleported content, and wrapper-owned state, load component mechanics.
For package boundaries, dependency direction, and page or component ownership, load application architecture.
Page Structure Boundary¶
This page owns how elements occupy and share space after the correct components and behavior have been chosen. Typical concerns include:
- page shells, content-width constraints, columns, rows, and grid tracks
- width, height, spacing, alignment, wrapping, overflow, and scroll ownership
- font resources, font families, type sizes, weights, line height, and line length
- responsive page composition and stable control dimensions
- rem-based sizing, browser text enlargement, and explicit element scaling
- exceptional CSS that cannot be expressed through Python mechanics or Tailwind classes
The companion component mechanics reference owns how behavior crosses framework boundaries. Use it when the question is whether a value belongs in a constructor, Quasar prop, Vue event, slot, method, binding, or teleported popup.
Precedence: Python, Then Tailwind, Then CSS¶
Apply this order to every structural requirement:
- Use NiceGUI's Python composition and component APIs: containers such as
ui.row,ui.column, andui.grid, constructor arguments, documented properties, slots, and wrapper methods. - Add Tailwind classes through
.classes(...)for width, tracks, spacing, alignment, wrapping, overflow, responsive changes, typography, and other physical presentation. - Use Quasar props or helper classes when the requirement belongs specifically to a Quasar component and NiceGUI exposes that boundary.
- Use
.style(...)only for a calculated runtime value that cannot be represented by the available APIs or utility classes. - Add scoped static CSS only when all preceding layers cannot express the requirement without relying on unsupported component internals.
Do not create a stylesheet merely to rename or group utilities that fit cleanly in .classes(...). Reuse a Python component or helper when a class sequence repeats. Before adding CSS, identify the unsupported requirement it solves; if the rule needs selectors such as .q-field__control, changes popup positioning, or depends on generated Vue markup, resolve the component mechanics first instead of compensating with CSS.
(
ui.select(options=items, label="Item")
.props("outlined")
.classes("w-full md:max-w-md rounded")
)
Physical Layout Model¶
Four layout decisions control most NiceGUI page structure:
| Decision | Typical declarations | Failure when omitted |
|---|---|---|
| outer constraint | w-full, max-w-*, mx-auto, px-* |
content touches viewport edges or becomes unreadably wide |
| track sizing | flex-1, shrink-0, grid-cols-*, minmax(0, 1fr) |
sidebars collapse or content forces tracks wider than the viewport |
| intrinsic minimums | min-w-0, min-h-0 |
flexible children refuse to shrink and create page-level overflow |
| overflow owner | overflow-auto, overflow-x-auto, overflow-hidden |
multiple nested scrollers or clipped interactive content |
NiceGUI rows and columns provide component structure, while their .classes(...) values define the physical constraints. Prefer explicit Tailwind p-* and gap-* classes for local container spacing. NiceGUI's --nicegui-default-padding and --nicegui-default-gap variables, both 1rem by default, are CSS-level exceptions for changing the framework-wide baseline rather than one container.
Structural Styling With Tailwind¶
NiceGUI's .classes() method attaches Tailwind-compatible classes directly to the rendered element. The structural categories used most often are:
| Concern | Representative classes |
|---|---|
| display and tracks | flex, grid, grid-cols-1, md:grid-cols-2 |
| growth and shrinkage | flex-1, grow, shrink-0, basis-* |
| dimensions | w-full, h-full, min-w-0, max-w-6xl, size-10 |
| spacing | gap-4, px-4, py-6, mx-auto, space-y-3 |
| alignment | items-start, items-center, justify-between, self-stretch |
| wrapping and overflow | flex-wrap, whitespace-nowrap, overflow-auto, truncate |
| positioning | relative, absolute, sticky, inset-*, z-* |
| responsive changes | md:flex-row, lg:grid-cols-3, xl:max-w-7xl |
The Tailwind width and maximum-width references distinguish fixed spacing-scale widths, fractions, viewport units, and container-scale constraints. A centered shell normally combines its responsibilities explicitly:
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")
w-full fills available inline space, max-w-6xl caps line and panel length, mx-auto centers the shell, and px-4 retains edge space below the cap. Inside the row, shrink-0 protects the sidebar and min-w-0 allows the flexible content track to become narrower than its intrinsic content.
Tailwind's responsive variants are mobile-first. Unprefixed classes apply at every size; md:* and larger prefixes apply from their minimum width upward. In NiceGUI's default Tailwind setup, verify available classes against the framework version bundled by the installed NiceGUI release. Optional UnoCSS presets are intentionally not fully compatible with Tailwind, and Tailwind CSS layers are one documented difference.
Combine Tailwind With Quasar Utilities Deliberately¶
NiceGUI's .classes() accepts both Tailwind utilities and CSS helpers bundled with Quasar. Assign each concern to one system:
| Concern | Default owner | Examples |
|---|---|---|
| Python element structure | NiceGUI | ui.row, ui.column, ui.grid, slot context managers |
| Generic geometry and responsive layout | Tailwind | w-full, items-center, justify-center, gap-2, p-6, flex-wrap |
| Generic application typography | Tailwind | text-xl, font-medium, leading-6, truncate |
| Theme-aware semantic color | Quasar | text-primary, bg-positive, text-grey-7 |
| Component behavior and density | Quasar props | dense, outlined, round, separator=horizontal |
| Component-specific geometry | Quasar helper classes | q-table--col-auto-width, absolute-top-right |
Prefer the Tailwind spelling when both systems express ordinary application layout or typography. For example, use w-full instead of full-width, items-center justify-center instead of flex-center, gap-2 instead of q-gutter-sm, p-4 instead of q-pa-md, and text-xl font-medium instead of text-h6 text-weight-medium. This keeps spacing, breakpoints, and type choices in one vocabulary.
Quasar helpers remain useful when a value intentionally follows Quasar's component conventions:
q-m*andq-p*spacing classes when spacing should follow Quasar's component scale- typography helpers, such as
text-h6,text-subtitle2, andtext-weight-medium, for text that should follow Quasar's type system - visibility helpers, such as
gt-smandlt-md, when visibility should use Quasar's configured breakpoints - positioning helpers, such as
absolute-top-right, when positioning content relative to a Quasar component - size and overflow helpers, such as
fit,full-width, andoverflow-auto, when matching Quasar layout behavior
Do not assign the same property through both systems on one element. For example, w-full q-pa-md deliberately uses Tailwind for width and Quasar for component-scale padding; adding p-4 would create competing padding declarations. The same rule applies to Tailwind and Quasar visibility helpers or to Tailwind font sizes and Quasar heading classes.
with ui.card().classes("w-full max-w-2xl q-pa-md"):
ui.label("Inventory summary").classes("text-h6")
ui.label("12 locations").classes("text-subtitle2 text-weight-medium")
Tailwind and Quasar do not share breakpoint thresholds. Tailwind's defaults begin sm at 40rem and md at 48rem; Quasar defines sm from 600px and md from 1024px. Keep one breakpoint system responsible for a given layout transition, and confirm the bundled framework versions before relying on exact thresholds.
CSS As A Last Resort¶
Do not move stable geometry into a stylesheet simply because a Tailwind class string is long. Tailwind arbitrary values can express constraints such as minmax(...), min(...), aspect ratios, and dynamic viewport units while keeping the rule visible beside the Python structure that owns it.
with ui.element("main").classes(
"grid min-h-0 "
"grid-cols-[minmax(14rem,20rem)_minmax(0,1fr)]"
):
sidebar()
workspace().classes("min-w-0")
ui.select(...).props(
'outlined popup-content-class="max-h-[min(24rem,60dvh)] overflow-y-auto"'
).classes("w-full md:max-w-md")
Use .style() only when a value is calculated at runtime and no class or component property can represent it. Keep the override on the narrowest element and do not promote it to a shared stylesheet unless it becomes a genuine cross-component rule.
Static CSS remains appropriate for browser-level facilities such as @font-face, selectors or pseudo-elements with no available utility, and integration with markup that cannot receive classes. Attach an application-owned class through .classes() or a documented Quasar prop, then scope the exceptional rule beneath that class.
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.
Fonts And Typography¶
Typography affects physical layout because font metrics determine line breaks, control height, baseline alignment, and the intrinsic width of labels. Treat font loading and the type scale as structural dependencies rather than late decoration.
Font Families And Loading¶
Tailwind provides font-sans, font-serif, and font-mono, and supports custom family utilities as documented by Tailwind font family. Quasar's typography reference documents its embedded Roboto default and its heading, weight, alignment, wrapping, and case helpers.
For an application-owned typeface, @font-face is one of the browser-level cases that warrants CSS. Mount the font with other static assets and declare it once in the shared stylesheet. MDN @font-face recommends WOFF2 for modern web delivery; font-display: swap keeps text available while the resource loads.
@font-face {
font-family: "App Sans";
src: url("/static/fonts/app-sans.woff2") format("woff2");
font-display: swap;
font-style: normal;
font-weight: 400 700;
}
.app-shell {
font-family: "App Sans", sans-serif;
}
Include the real weight range supplied by the font file. Requesting an unavailable weight makes the browser synthesize it and can alter text width. Keep a fallback family so failed or delayed font requests do not leave text unavailable.
Type Size And Line Height¶
Tailwind's font-size utilities pair named rem-based sizes such as text-sm, text-base, and text-xl with default line heights. Combined forms such as text-sm/6 set size and line height together. Separate leading-*, font-*, and text-alignment utilities refine those dimensions.
with ui.column().classes("w-full max-w-[65ch] gap-3"):
ui.label("Inventory summary").classes("text-2xl/8 font-semibold")
ui.label("Counts by location and storage area").classes("text-base/7")
Prefer a small named hierarchy over unrelated one-off sizes. Use rem-based utilities so browser font preferences and page zoom remain meaningful, and use a character-based maximum width such as max-w-[65ch] for long prose. Avoid viewport-width font sizing: text should reflow at narrow widths rather than shrink to preserve one line.
em dimensions inherit and can compound through nested elements; rem dimensions refer to the root element and avoid that compounding. The MDN font-size reference describes both behaviors and recommends relative sizing for accessibility.
Scaling Boundaries¶
The word "scale" can refer to different browser mechanics. They are not interchangeable:
| Mechanism | Participates in layout | Appropriate use |
|---|---|---|
| responsive classes and reflow | yes | normal page adaptation across available widths |
| relative font and spacing units | yes | coherent type and spacing changes that respect browser settings |
| browser zoom | yes, at the document level | user-controlled magnification that the page must tolerate |
CSS zoom |
yes | exceptional magnification of a bounded region |
transform: scale(...) |
no | transient visual emphasis or a deliberately overlaid preview |
Responsive reflow through Python composition and Tailwind classes is the default for page structure. A narrower page should stack tracks, wrap controls, and retain readable text rather than shrink the entire interface.
CSS zoom changes the size used by layout, so surrounding content is recalculated. transform: scale() changes only painting; neighboring elements retain the unscaled geometry, and enlarged content can overlap or overflow its box. Treat both as exceptional effects after responsive widths, gaps, and breakpoints have been exhausted.
Stable fixed-format regions such as boards, diagrams, and previews need an explicit box before their contents scale. Combine aspect-ratio, a bounded inline size, and local overflow rules so transformed content cannot resize surrounding controls. Scaling animations should respect prefers-reduced-motion.
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. Unprefixed Tailwind classes define the mobile baseline; breakpoint-prefixed classes alter it at larger widths. Prefer flex wrapping and fluid grids before adding another breakpoint. Use Tailwind classes for page layout and Quasar props for component density and behavior.
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.
Height needs an explicit ownership chain. h-full only resolves when the containing block has a definite height; viewport-bound workspaces usually need a defined outer height and min-h-0 on nested flex or grid tracks before an inner overflow-auto region can scroll. Prefer dynamic viewport units such as dvh for browser UI that changes the visible mobile viewport.
Loading Exceptional CSS And Static Assets¶
- Keep ordinary layout and typography in Python mechanics and Tailwind classes rather than creating a stylesheet.
- When exceptional CSS is required, mount and link it once from the composition layer rather than injecting it from individual pages.
- Keep custom dimensions and font families in named variables or application classes.
- Avoid broad rules against Quasar internals.
- Mount referenced stylesheets, fonts, and other assets in the composition layer.
- Verify mount paths, reverse-proxy rewrites, and cache behavior.
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,
)
Mechanics-Sensitive Visual Cases¶
Some visual requests depend on framework behavior before CSS can be chosen safely. Use component mechanics for detached menus and dialogs, named slots, icon asset families, Quasar internal geometry, frontend methods, and server-client state synchronization. Its select example shows how a NiceGUI scoped slot preserves QSelect's option interaction contract without custom CSS.
Validation Checklist¶
Check each completed page at these three viewports:
- A representative mobile viewport, such as \(390 \times 844\).
- Landscape desktop at \(1920 \times 1080\).
- 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. Repeat the checks with browser zoom or text enlargement, a delayed font request, long labels, validation messages, and loaded content. Watch for unexpected page-level horizontal scrolling, nested scroll regions, clipped focus outlines, and layout shifts when the webfont replaces its fallback.
Sources¶
Primary sources
- NiceGUI element styling and props
- NiceGUI binding properties
- NiceGUI styling and appearance
- Quasar components
- Quasar spacing classes
- Quasar typography helpers
- Quasar breakpoints
- Quasar visibility helpers
- Quasar positioning helpers
- Quasar size and overflow helpers
- Quasar field
- Quasar select
- Tailwind width utilities
- Tailwind maximum-width utilities
- Tailwind font-family utilities
- Tailwind font-size utilities
- Tailwind responsive design
- MDN
@font-face - MDN
font-size - MDN
zoom - MDN
scale()