diff --git a/.github/styles/config/vocabularies/Docs/accept.txt b/.github/styles/config/vocabularies/Docs/accept.txt index 79e0beeb1c..b11856162b 100644 --- a/.github/styles/config/vocabularies/Docs/accept.txt +++ b/.github/styles/config/vocabularies/Docs/accept.txt @@ -285,3 +285,10 @@ XSS # Names of React hooks useForm + +# Layout and CSS terms +[cC]olspans? +[rR]eflows? +varargs +XLarge +XXLarge diff --git a/articles/building-apps/ui-basics/layouts/css-grid.adoc b/articles/building-apps/ui-basics/layouts/css-grid.adoc new file mode 100644 index 0000000000..56e707bbb5 --- /dev/null +++ b/articles/building-apps/ui-basics/layouts/css-grid.adoc @@ -0,0 +1,114 @@ +--- +title: CSS Grid Layouts +page-title: How to use CSS Grid in a Vaadin application +description: Learn how to arrange Vaadin components in two-dimensional grids using CSS Grid, including fluid card grids that wrap automatically. +meta-description: Arrange Vaadin components into rows and columns with CSS Grid, including fluid card grids that wrap automatically without media queries. +order: 20 +--- + += CSS Grid Layouts +:toclevels: 2 + +CSS Grid is a _two-dimensional_ layout system: it arranges children into rows *and* columns at the same time. Use it when the one-dimensional layouts -- `HorizontalLayout` and `VerticalLayout`, which arrange a single row or column -- aren't enough, such as for card grids or fixed page grids. For those one-dimensional layouts, see the <> overview. + +CSS Grid is plain CSS, so it works with any theme. Vaadin has no dedicated grid layout component; instead you apply grid styling to an ordinary container. + +.CSS Grid is not the Vaadin Grid component +[IMPORTANT] +This article is about _CSS Grid_, the browser layout system. It is unrelated to the Vaadin <> component, which is a data table for displaying rows of items. + + +== When to Use CSS Grid + +[cols="2,1",options="header"] +|=== +| Need | Use +| A single row or column of components | <> +| Form fields with labels | <> +| User-rearrangeable dashboard widgets | <> +| A fixed two-dimensional grid, or a card grid | CSS Grid (this article) +|=== + + +== Applying CSS Grid + +Because there's no grid layout component, add `display: grid` to a container through a CSS class, then add the components as its children -- each child becomes a cell: + +[source,java] +---- +Div productGrid = new Div(); +productGrid.addClassName("product-grid"); +productGrid.add(card1, card2, card3, card4, card5, card6); +---- + +[source,css] +---- +.product-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); /* three equal-width columns */ + gap: 1rem; /* space between cells */ +} +---- + +`1fr` is a _fraction_ of the available space, so `repeat(3, 1fr)` makes three columns that share the width equally. Rows are created automatically as items fill the columns. Use `gap` for spacing between cells rather than margins on the children. + +For how to attach a stylesheet and apply class names from Java, see <<../add-styling#,Add Styling>>. + + +[#fluid-grids-that-wrap-automatically] +== Fluid Grids That Wrap Automatically + +For a card grid that should fit as many columns as the screen allows, combine `repeat()` with `auto-fill` and `minmax()`. This adapts to the available width *without any media queries*: + +[source,css] +---- +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 1rem; +} +---- + +Each column is at least `220px` wide and grows to fill leftover space; the browser packs in as many columns as fit and wraps the rest to new rows. For most card grids this is simpler and smoother than defining breakpoints by hand. When you do need breakpoint-based changes, see <>. + + +== Spanning Multiple Cells + +A child can span several columns or rows. Add a class to the component and set `grid-column` or `grid-row`: + +[source,java] +---- +featuredCard.addClassName("featured"); +---- + +[source,css] +---- +.featured { + grid-column: span 2; /* take up two columns */ +} +---- + + +== Lumo Utility Classes + +[NOTE] +Utility classes require the *Lumo theme*. With the Aura theme, write the CSS shown above. + +If you use Lumo, you can build a grid without a separate stylesheet by applying utility classes from Java -- `LumoUtility.Display.GRID` together with the grid-column and `LumoUtility.Gap` utilities: + +[source,java] +---- +productGrid.addClassNames( + LumoUtility.Display.GRID, + LumoUtility.Gap.MEDIUM); +---- + +For the full list of grid and gap utility classes, see the <> reference. + + +== Best Practices + +. *Use CSS Grid for two-dimensional layouts only.* For a single row or column, `HorizontalLayout` or `VerticalLayout` is simpler -- see <>. +. *Prefer `auto-fill` with `minmax()` for card grids.* It wraps fluidly without media queries; reach for breakpoints only when the layout must change in a way `minmax()` can't express. +. *Use `gap` for spacing,* not margins on the children. +. *Don't confuse CSS Grid with the Vaadin `Grid` component* -- one is a layout system, the other a data table. diff --git a/articles/building-apps/ui-basics/layouts.adoc b/articles/building-apps/ui-basics/layouts/index.adoc similarity index 93% rename from articles/building-apps/ui-basics/layouts.adoc rename to articles/building-apps/ui-basics/layouts/index.adoc index 66dd4234be..90012a2f55 100644 --- a/articles/building-apps/ui-basics/layouts.adoc +++ b/articles/building-apps/ui-basics/layouts/index.adoc @@ -83,14 +83,13 @@ The main area scrolls because it's wrapped in a `Scroller`. For that to work in | Single row, side by side | `HorizontalLayout` | — | Form fields with labels | `FormLayout` | A `VerticalLayout` of fields | Application shell: drawer, header, content | <> | Nested `HorizontalLayout`/`VerticalLayout` -| Grid of cards or dashboard widgets | <> or CSS Grid | Wrapped `HorizontalLayout` rows -| True two-dimensional grid (rows *and* columns) | CSS Grid | Multiple rows in a `VerticalLayout` -| Toggle horizontal/vertical at runtime | <> | Swapping between the two layouts +| Grid of cards or dashboard widgets | <> or <> | Wrapped `HorizontalLayout` rows +| True two-dimensional grid (rows *and* columns) | <> | Multiple rows in a `VerticalLayout` |=== .Don't rebuild specialized components by hand [IMPORTANT] -Building an app shell out of nested layouts, or a card grid out of wrapping rows, is a common mistake that produces worse results with more code. `AppLayout` handles responsive drawers and navbar placement; `Dashboard` and CSS Grid give proper two-dimensional control and reflow. Reach for them instead. +Building an app shell out of nested layouts, or a card grid out of wrapping rows, is a common mistake that produces worse results with more code. `AppLayout` handles responsive drawers and navbar placement; `Dashboard` and <> give proper two-dimensional control and reflow. Reach for them instead. == The Two Layouts @@ -126,7 +125,7 @@ The defaults of the two layouts are *asymmetric*, and this is the single most co .Watch the width asymmetry when nesting [WARNING] -A `VerticalLayout` defaults to 100% width while a `HorizontalLayout` hugs its content. So a `VerticalLayout` nested inside a `HorizontalLayout` tries to take the full parent width, which is often not what you want. When composing layouts, set sizes explicitly rather than relying on the defaults. +A `VerticalLayout` defaults to 100% width while a `HorizontalLayout` hugs its content, so a `VerticalLayout` nested inside a `HorizontalLayout` tries to take the full parent width, which is often not what you want. When composing layouts, set sizes explicitly rather than relying on the defaults. Spacing, padding, and margin are toggled with `setSpacing()`, `setPadding()`, and `setMargin()`. To change the *size* of the gap, use a spacing theme variant or the layout's CSS custom properties -- see the <> for the available variants and properties. @@ -186,7 +185,7 @@ layout.setFlexGrow(1, sidePanel); // mainPanel gets 2/3, sidePanel gets 1/3 [WARNING] These are methods on the *parent layout*, taking the child as an argument -- there is no `child.setFlexGrow(1)`. The child argument is varargs, so `layout.setFlexGrow(1)` (with the child forgotten) compiles but *silently does nothing*. If a flex setting has no effect, first confirm you passed the target component. -*The shrinking trap.* `setWidthFull()` sets a literal `width: 100%`, which inside a flex layout means "100% of the parent," not "the remaining space." Because every flex child defaults to `flex-shrink: 1`, a full-width component next to a fixed-size sibling makes *both* shrink -- so the fixed sibling ends up smaller than specified. Two reliable fixes: +*The shrinking trap.* `setWidthFull()` sets a literal `width: 100%`, which inside a flex container means "100% of the parent," not "the remaining space." Because every flex child defaults to `flex-shrink: 1`, a full-width component next to a fixed-size sibling makes *both* shrink -- so the fixed sibling ends up smaller than specified. Two reliable fixes: [source,java] ---- @@ -260,4 +259,6 @@ Extra whitespace around content:: Disable padding and spacing on inner layouts ( A `setFlexGrow()` or alignment call does nothing:: Confirm it was called on the *parent* with the child passed as an argument, and that the layout is larger than its content along the relevant axis. -For text, headings, and semantic structure, see <>. If you're comfortable with HTML and CSS, you can also use `Div` with CSS flexbox or grid for layouts beyond what these components offer. +To adapt these layouts to different screen sizes, see <>. + +For text, headings, and semantic structure, see <<../write-html#,Write HTML>>. If you're comfortable with HTML and CSS, you can also use `Div` with CSS flexbox or grid for layouts beyond what these components offer. diff --git a/articles/building-apps/ui-basics/layouts/responsive.adoc b/articles/building-apps/ui-basics/layouts/responsive.adoc new file mode 100644 index 0000000000..8a63505620 --- /dev/null +++ b/articles/building-apps/ui-basics/layouts/responsive.adoc @@ -0,0 +1,169 @@ +--- +title: Responsive Layouts +page-title: How to build responsive layouts in Vaadin +description: Learn how to adapt Vaadin layouts to different screen sizes with built-in components, CSS media and container queries, and Lumo utility classes. +meta-description: Build Vaadin views that adapt to phones, tablets, and desktops using built-in responsive components, CSS media and container queries, and Lumo utility classes. +order: 10 +--- + += Responsive Layouts +:toclevels: 2 + +A _responsive_ layout adapts to the size of the screen it's shown on, presenting the right amount of information and interaction for each device. This guide covers how to *implement* a responsive Vaadin view. For the design decisions behind responsiveness -- when to reduce features, go mobile-first, or build a separate mobile UI -- see <>. For the layout components themselves -- rows, columns, sizing, and alignment -- see the <> overview. + +Responsiveness is a styling concern, handled with CSS rather than by detecting the screen size on the server. The core techniques -- CSS media queries and container queries -- work with *any theme*. The Lumo theme adds utility classes as a shortcut; with the Aura theme, which has no utility classes, you use the CSS techniques directly. + + +== Use Built-In Responsive Components First + +Many Vaadin components already adapt themselves, so check whether one fits before writing any responsive CSS. For example, <> collapses its drawer to a hamburger menu, <> adjusts its column count, <> reflows its widgets, and <> moves overflowing items into a sub-menu. For the full catalog of components with built-in responsive behavior, see <>. + + +== CSS Media Queries + +A _media query_ applies a block of CSS only when the screen matches a condition, such as a maximum width. This is the primary tool for adapting to the viewport size. + +Add a class name to the component from Java, then write the responsive rules in your view's stylesheet: + +[source,java] +---- +filterPanel.addClassName("filter-panel"); +---- + +[source,css] +---- +.filter-panel { + display: flex; +} + +/* Hide the panel on screens 640px wide or narrower */ +@media (max-width: 640px) { + .filter-panel { + display: none; + } +} +---- + +For how to attach a stylesheet and apply class names from Java, see <<../add-styling#,Add Styling>>. + +To keep behavior predictable, use a consistent set of breakpoints rather than targeting specific device widths: + +[cols="1,1,2",options="header"] +|=== +| Breakpoint | Min width | Typical device +| (default) | 0px | Phones (portrait) +| Small | 640px | Phones (landscape), small tablets +| Medium | 768px | Tablets (portrait) +| Large | 1024px | Tablets (landscape), small laptops +| XLarge | 1280px | Laptops and desktops +| XXLarge | 1536px | Large and high-resolution monitors +|=== + +Use the min width in a CSS media query: `@media (min-width: 768px)` targets the `Medium` breakpoint and wider. The Lumo utility classes use these same breakpoints -- see <<#lumo-utility-classes,Lumo Utility Classes>> below. + +=== Pattern: Responsive Card Grid + +A common need is a grid of cards that shows more columns as the screen widens. <> handles this with a few media queries, and it works with any theme: + +[source,css] +---- +.card-grid { + display: grid; + gap: 1rem; + grid-template-columns: 1fr; /* one column on mobile */ +} + +@media (min-width: 640px) { + .card-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (min-width: 1024px) { + .card-grid { grid-template-columns: repeat(3, 1fr); } +} +---- + +For a card grid that wraps automatically without media queries, see <>. + + +== CSS Container Queries + +A _container query_ adapts a component to the width of its own container rather than the whole screen. Use it for resizable panels, dashboard widgets, and reusable components that may appear in different places -- anywhere the component shouldn't assume how much space it has. + +Mark an element as a container, then query its width: + +[source,css] +---- +.side-panel { + container-type: inline-size; + container-name: side-panel; +} + +/* Show the footer only when the panel itself is at least 400px wide */ +.side-panel .footer { + display: none; +} + +@container side-panel (min-width: 400px) { + .side-panel .footer { + display: flex; + } +} +---- + +This makes a component self-contained: it adapts to the space it's given, no matter where it's placed. + + +[#lumo-utility-classes] +== Lumo Utility Classes + +[NOTE] +Utility classes require the *Lumo theme*. If you use Aura, use CSS media or container queries instead, as shown above. + +If you use Lumo, its utility classes are the fastest way to add responsive behavior without writing custom CSS. They follow a _mobile-first_ pattern: the plain class sets the default (mobile) style, and a breakpoint-prefixed class overrides it on larger screens. + +Load the utility classes with `@StyleSheet` imports on your application shell: + +[source,java] +---- +@StyleSheet(Lumo.STYLESHEET) +@StyleSheet(Lumo.UTILITY_STYLESHEET) +public class Application implements AppShellConfigurator { +} +---- + +[NOTE] +When migrating from an older Vaadin version, remember that loading utility classes through `theme.json` is no longer supported -- use the `@StyleSheet` imports shown above. + +Apply the classes from Java with `addClassNames()`. For example, to show a toolbar only on small screens: + +[source,java] +---- +mobileToolbar.addClassNames( + LumoUtility.Display.FLEX, // visible by default (mobile) + LumoUtility.Display.Breakpoint.Medium.HIDDEN); // hidden at 768px and up +---- + +Or to stack a container vertically on mobile and switch to a row on wider screens: + +[source,java] +---- +container.addClassNames( + LumoUtility.Display.FLEX, + LumoUtility.FlexDirection.COLUMN, // stacked by default (mobile) + LumoUtility.FlexDirection.Breakpoint.Medium.ROW); // side by side at 768px and up +---- + +For the full list of utility classes and their breakpoint variants, see the <> reference. + + +== Best Practices + +. *Use CSS for responsiveness, not server-side detection.* Avoid making layout decisions from `Page.retrieveExtendedClientDetails()` or a browser-resize listener -- CSS media and container queries are faster and need no server round-trip. +. *Design mobile-first.* Make the mobile layout the default and add complexity for larger screens. This matches how the Lumo breakpoints work. +. *Lean on the built-in responsive components* -- App Layout, Form Layout, Dashboard, and Menu Bar already adapt. Don't rebuild what they provide. +. *Prefer container queries for reusable components.* If a component might appear in containers of different widths, container queries make it self-adapting. +. *Design for fluid sizes, not specific devices.* Users resize windows, split screens, and zoom. Test at many widths, not only "phone" and "desktop." +. *Check wrapping at in-between sizes.* When you rely on `setWrap(true)` or CSS `flex-wrap`, verify that items wrap gracefully between your breakpoints, not only at them. + +[WARNING] +Don't add a server-side browser-resize listener to switch layouts -- it sends a request to the server on every resize. And avoid building separate mobile and desktop view classes unless the interaction patterns are genuinely different; one view with responsive CSS is far easier to maintain. diff --git a/articles/designing-apps/responsiveness.adoc b/articles/designing-apps/responsiveness.adoc index ff2212dd95..d0e19b89ea 100644 --- a/articles/designing-apps/responsiveness.adoc +++ b/articles/designing-apps/responsiveness.adoc @@ -75,6 +75,7 @@ The following image demonstrates that only 75% of the height is actually availab image::images/responsiveness-browser-size.png[Height of browser content area is 697px while screen height is 900px] +[#responsive-features-in-vaadin] == Responsive Features in Vaadin Some <> have responsive features built into them. For example, the position of the <> overlay is changed to optimize for touchscreen devices. This is demonstrated in the following image, with the desktop version on the left, and the mobile version on the right. On the desktop version, the overlay shows up below the field itself. On the mobile version, the overlay is fixed to the bottom of the screen: @@ -115,7 +116,7 @@ image::images/responsiveness-native-inputs.png[Time Picker components in Vaadin, == Developing Responsive User Interfaces -The Vaadin components use standard web technologies to implement responsiveness: flexbox, grid, media queries, and container queries. You use the same technologies to make your user interfaces responsive. If you are unfamiliar with CSS, Vaadin also provides some utilities for using them. +The Vaadin components use standard web technologies to implement responsiveness: flexbox, grid, media queries, and container queries. You use the same technologies to make your user interfaces responsive, and if you are unfamiliar with CSS, the Lumo theme also provides utility classes for them. This section introduces these techniques from a design standpoint. For how to apply them in a Vaadin view -- with code for both the Lumo and Aura themes -- see <> in the Building Apps guide. === Flexbox and Grid @@ -131,83 +132,20 @@ Don't confuse the CSS grid layout system with the Vaadin <> already use flexbox and grid under the hood. This makes them easier to use even if you are not familiar with how the CSS works. If the Vaadin layout components don't fit your specific needs, you can write your own CSS or use the <>. +In Vaadin, the <> already use flexbox and grid under the hood. This makes them easier to use even if you are not familiar with how the CSS works. If the Vaadin layout components don't fit your specific needs, you can write your own CSS or use the <>. For how to build two-dimensional grids with CSS Grid in a Vaadin view, see <>. === CSS Media Query and Container Query -Media queries allow you to apply CSS styles based on the characteristics of the device that is used to access the application. When building responsive user interfaces, the most common characteristics you'll use are the width and height of the viewport. +Media queries apply CSS styles based on characteristics of the device, most commonly the width of the viewport. You use them to adapt the UI to the screen size -- for example, hiding a desktop-only toolbar on narrow screens or revealing a mobile-only one. For more information, see the https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries[MDN web docs for media queries]. -In the following example, the mobile toolbar is invisible by default. The media query overrides the styles and makes the toolbar visible when the viewport width is equal to or narrower than 640px. In practice, this means that the toolbar is hidden on non-mobile devices and visible on mobile devices: - -[source,css] ----- -.mobile-toolbar { - display: none; -} - -@media (max-width: 640px) { - .mobile-toolbar { - display: flex; - } -} ----- - -For more information, see the https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries[MDN web docs for media queries]. - -Sometimes, styling based on the viewport width isn't enough. For instance, if an application has resizable content areas, it's desirable to style the content based on the width of the content area rather than the width of the screen. Container queries allow you to do this. - -In the following example, the side panel is resizable. It has a footer that is hidden by default. The container query overrides the styles and makes the footer visible when the side panel width is equal to or wider than 400px. - -[source,css] ----- -.sidepanel { - container-type: inline-size; - container-name: sidepanel; /* Optional */ -} - -.sidepanel .footer { - display: none; -} - -@container sidepanel (min-width: 400px) { - .footer { - display: flex; - } -} ----- - -For more information, see the https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries[MDN web docs for container queries]. +Sometimes styling based on the viewport width isn't enough. If an application has resizable content areas, it's often better to style the content based on the width of the content area rather than the whole screen. Container queries allow this, so a component adapts to its own container no matter where it's placed. For more information, see the https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries[MDN web docs for container queries]. === Lumo Utility Classes -The <> are small, single-purpose CSS classes that can be applied directly to a component or HTML element to style it in a specific way. Unlike traditional CSS approaches, which often involve writing custom styles for each element, utility classes offer a more modular and reusable way to style your content. - -The responsive Lumo utility classes follow a mobile-first approach. This means that the default styles should optimize for mobile viewports. You then add extra styles for larger viewports through something called _breakpoints_. Breakpoints target various minimum screen widths. For instance, the `Small` breakpoint applies to screens that are 640px or wider, whereas the `Medium` breakpoint applies to screens that are 768px or wider. - -In the following example, the mobile toolbar is visible by default. The `Small` breakpoint makes it hidden when the viewport width is equal to or wider than 640px: - -[.example] --- -[source,html] ----- - -
----- - -[source,java] ----- - -mobileToolbar.addClassNames(Display.FLEX, Display.Breakpoint.Small.HIDDEN); ----- -.TSX -[source,html] ----- - -
----- --- +The <> are small, single-purpose CSS classes that you apply directly to a component or HTML element, offering a modular, reusable alternative to writing custom styles. The responsive utility classes follow a mobile-first approach: the default styles optimize for mobile viewports, and _breakpoints_ add styles for larger screens. For instance, the `Small` breakpoint applies to screens 640px or wider, and `Medium` to screens 768px or wider. -For more information and examples, see the blog post https://vaadin.com/blog/building-responsive-layouts-with-vaadin-utility-classes[Building responsive layouts with Vaadin utility classes]. +[NOTE] +Utility classes are specific to the Lumo theme. With the Aura theme, use CSS media and container queries instead. -// TODO The blog post contents should be incorporated into the Desiging Apps guide, maybe as a deep dive. +For the code to apply these techniques in a Vaadin view, see <>.