Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions articles/building-apps/ui-basics/layouts/css-grid.adoc
Original file line number Diff line number Diff line change
@@ -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 <<index#,Arrange with Layouts>> 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 <</components/grid#,Grid>> 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 | <<index#,HorizontalLayout / VerticalLayout>>
| Form fields with labels | <</components/form-layout#,Form Layout>>
| User-rearrangeable dashboard widgets | <</components/dashboard#,Dashboard>>
| 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 <<responsive#,Responsive Layouts>>.


== 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 <</styling/themes/lumo/utility-classes#,Lumo Utility Classes>> reference.


== Best Practices

. *Use CSS Grid for two-dimensional layouts only.* For a single row or column, `HorizontalLayout` or `VerticalLayout` is simpler -- see <<index#,Arrange with Layouts>>.
. *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.
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ 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 | <</components/app-layout#,App Layout>> | Nested `HorizontalLayout`/`VerticalLayout`
| Grid of cards or dashboard widgets | <</components/dashboard#,Dashboard>> or CSS Grid | Wrapped `HorizontalLayout` rows
| True two-dimensional grid (rows *and* columns) | CSS Grid | Multiple rows in a `VerticalLayout`
| Grid of cards or dashboard widgets | <</components/dashboard#,Dashboard>> or <<css-grid#,CSS Grid>> | Wrapped `HorizontalLayout` rows
| True two-dimensional grid (rows *and* columns) | <<css-grid#,CSS Grid>> | Multiple rows in a `VerticalLayout`
| Toggle horizontal/vertical at runtime | <</components/flex-layout#,Flex Layout>> | Swapping between the two layouts
|===

.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 <<css-grid#,CSS Grid>> give proper two-dimensional control and reflow. Reach for them instead.


== The Two Layouts
Expand Down Expand Up @@ -260,4 +260,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 <<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.
To adapt these layouts to different screen sizes, see <<responsive#,Responsive Layouts>>.

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.
166 changes: 166 additions & 0 deletions articles/building-apps/ui-basics/layouts/responsive.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
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 <</designing-apps/responsiveness#,Responsiveness>>. For the layout components themselves -- rows, columns, sizing, and alignment -- see the <<index#,Arrange with Layouts>> 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, <</components/app-layout#,App Layout>> collapses its drawer to a hamburger menu, <</components/form-layout#,Form Layout>> adjusts its column count, <</components/dashboard#,Dashboard>> reflows its widgets, and <</components/menu-bar#,Menu Bar>> moves overflowing items into a sub-menu. For the full catalog of components with built-in responsive behavior, see <</designing-apps/responsiveness#responsive-features-in-vaadin,Responsive Features in Vaadin>>.

Check failure on line 19 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'reflows'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'reflows'?", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 19, "column": 309}}}, "severity": "ERROR"}


== 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. These match Vaadin's Lumo utility classes (see below):

[cols="1,1,1,1",options="header"]
|===
| Breakpoint | Min width | CSS prefix | Lumo Java constant
| (default) | 0px | (none) | `LumoUtility.Display.*`
| Small | 640px | `sm:` | `*.Breakpoint.Small.*`

Check warning on line 55 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Spacing] 't.S' should have one space. Raw Output: {"message": "[Vaadin.Spacing] 't.S' should have one space.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 55, "column": 39}}}, "severity": "WARNING"}
| Medium | 768px | `md:` | `*.Breakpoint.Medium.*`

Check warning on line 56 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Spacing] 't.M' should have one space. Raw Output: {"message": "[Vaadin.Spacing] 't.M' should have one space.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 56, "column": 40}}}, "severity": "WARNING"}
| Large | 1024px | `lg:` | `*.Breakpoint.Large.*`

Check warning on line 57 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Spacing] 't.L' should have one space. Raw Output: {"message": "[Vaadin.Spacing] 't.L' should have one space.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 57, "column": 40}}}, "severity": "WARNING"}
| XLarge | 1280px | `xl:` | `*.Breakpoint.XLarge.*`

Check warning on line 58 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Spacing] 't.X' should have one space. Raw Output: {"message": "[Vaadin.Spacing] 't.X' should have one space.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 58, "column": 41}}}, "severity": "WARNING"}

Check failure on line 58 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'XLarge'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'XLarge'?", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 58, "column": 3}}}, "severity": "ERROR"}
| XXLarge | 1536px | `xxl:` | `*.Breakpoint.XXLarge.*`

Check warning on line 59 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Spacing] 't.X' should have one space. Raw Output: {"message": "[Vaadin.Spacing] 't.X' should have one space.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 59, "column": 43}}}, "severity": "WARNING"}

Check failure on line 59 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'XXLarge'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'XXLarge'?", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 59, "column": 3}}}, "severity": "ERROR"}
|===

=== Pattern: Responsive Card Grid

A common need is a grid of cards that shows more columns as the screen widens. <<css-grid#,CSS Grid>> 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-grid#fluid-grids-that-wrap-automatically,CSS Grid Layouts>>.


== 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

[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]
If you're migrating from an older Vaadin version, note that loading utility classes through `theme.json` is no longer supported -- use the `@StyleSheet` imports shown above.

Check warning on line 132 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.NoteThat] Avoid using 'note that'. Raw Output: {"message": "[Vaadin.NoteThat] Avoid using 'note that'.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 132, "column": 51}}}, "severity": "WARNING"}

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 <</styling/themes/lumo/utility-classes#,Lumo Utility Classes>> 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 just "phone" and "desktop."

Check warning on line 162 in articles/building-apps/ui-basics/layouts/responsive.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.JustSimply] Avoid using 'just'. It may be insensitive. Raw Output: {"message": "[Vaadin.JustSimply] Avoid using 'just'. It may be insensitive.", "location": {"path": "articles/building-apps/ui-basics/layouts/responsive.adoc", "range": {"start": {"line": 162, "column": 123}}}, "severity": "WARNING"}
. *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.
Loading
Loading