LayoutGrid gives you a responsive layout system built on a 12-column grid. Define how many columns each item spans at each screen size, and the layout shifts automatically — no media queries, no CSS wrestling.
Why Responsive Design Matters
Every application reaches users across a wide range of devices. A single layout rarely serves them all well:
| Device | Screen Width | User Context |
|---|---|---|
| Smartphone | < 600 px | On the go, touch input, limited space |
| Tablet | 600 – 1024 px | Mixed use, touch or keyboard, moderate space |
| Desktop | > 1024 px | Focused work, mouse and keyboard, plenty of space |
Responsive design lets you write one layout that adapts to all of them.
Breakpoints cascade upward through xs → sm → md → lg → xl; unset breakpoints inherit the nearest smaller value.
The 12-Column System
LayoutGrid divides the available width into 12 equal columns. Each item declares how many columns it occupies. The columns always add up to 12 per row; items that overflow wrap to the next row automatically.
| Columns | Proportional Width | Typical Use |
|---|---|---|
| 12 | 100 % | Full-width content, mobile layouts |
| 6 | 50 % | Two equal side-by-side columns |
| 4 | 33 % | Three equal columns |
| 3 | 25 % | Four-column grids, narrow sidebars |
Controls.LayoutGrid
.WithSkin(skin => skin.WithSpacing(2))
.WithView(Controls.Markdown("12 cols (100%)").AddSkin(Skins.Card), s => s.WithXs(12))
.WithView(Controls.Markdown("6 cols").AddSkin(Skins.Card), s => s.WithXs(6))
.WithView(Controls.Markdown("6 cols").AddSkin(Skins.Card), s => s.WithXs(6))
.WithView(Controls.Markdown("4").AddSkin(Skins.Card), s => s.WithXs(4))
.WithView(Controls.Markdown("4").AddSkin(Skins.Card), s => s.WithXs(4))
.WithView(Controls.Markdown("4").AddSkin(Skins.Card), s => s.WithXs(4))
Breakpoints: Adapting to Screen Size
A breakpoint is a screen-width threshold where the layout can change. LayoutGrid provides five, matching standard Material Design conventions:
| Method | Activates at | Screen type | Example devices |
|---|---|---|---|
.WithXs() |
< 600 px | Extra small | iPhones, Android phones |
.WithSm() |
≥ 600 px | Small | Large phones, small tablets |
.WithMd() |
≥ 960 px | Medium | iPads, tablets |
.WithLg() |
≥ 1280 px | Large | Laptops, small monitors |
.WithXl() |
≥ 1920 px | Extra large | Large monitors, 4 K displays |
Cascading upward. Settings inherit toward larger breakpoints. Setting
.WithMd(4)applies to medium screens and every larger size unless you explicitly override it with.WithLg()or.WithXl().
Designing for Small Screens (Smartphones)
Mobile screens are narrow. The guiding principle is stack first: make every item full-width on extra-small screens with WithXs(12), then introduce side-by-side columns as the screen grows.
Key guidelines:
- Stack vertically —
WithXs(12)ensures nothing squeezes awkwardly. - Prioritize content — the most important information should appear at the top.
- Touch-friendly targets — buttons and links need enough tap area.
Controls.LayoutGrid
.WithSkin(skin => skin.WithSpacing(2))
.WithView(Controls.Markdown("**Primary Content**\n\nFull width on mobile").AddSkin(Skins.Card), s => s
.WithXs(12) // Full width on phones
.WithMd(8)) // 2/3 width on tablets+
.WithView(Controls.Markdown("**Sidebar**\n\nBelow on mobile, side on tablet").AddSkin(Skins.Card), s => s
.WithXs(12) // Full width on phones (stacks below)
.WithMd(4)) // 1/3 width on tablets+ (appears beside)
Designing for Medium Screens (Tablets)
Tablets offer enough room for two-column layouts and are often used in both portrait and landscape orientations. A 3 + 9 or 4 + 8 split works well for navigation-plus-content patterns.
Key guidelines:
- Two-column layouts — ideal for master-detail or nav-plus-content views.
- Flexible splits —
6 + 6,4 + 8,3 + 9all fit naturally. - Consider orientation — portrait is meaningfully narrower than landscape.
Controls.LayoutGrid
.WithSkin(skin => skin.WithSpacing(3))
.WithView(Controls.Markdown("**Navigation**").AddSkin(Skins.Card), s => s
.WithXs(12) // Full width on phone
.WithMd(3)) // Narrow sidebar on tablet
.WithView(Controls.Markdown("**Main Content Area**\n\nMost of the screen on tablet").AddSkin(Skins.Card), s => s
.WithXs(12) // Full width on phone
.WithMd(9)) // Wide main area on tablet
Designing for Large Screens (Desktops)
Desktop users have space to spare — use it to show more information at once without overwhelming them. Card grids and multi-column panels shine here, but be mindful that very wide text lines are harder to read.
Key guidelines:
- Multi-column layouts — show collections in 3 or 4 column grids.
- Card grids — a classic
1 → 2 → 4progression scales cleanly across sizes. - Don't overstretch — constrain maximum reading width for text-heavy sections.
Controls.LayoutGrid
.WithSkin(skin => skin.WithSpacing(2))
.WithView(Controls.Markdown("Card 1").AddSkin(Skins.Card), s => s
.WithXs(12).WithSm(6).WithLg(3)) // 1 col -> 2 cols -> 4 cols
.WithView(Controls.Markdown("Card 2").AddSkin(Skins.Card), s => s
.WithXs(12).WithSm(6).WithLg(3))
.WithView(Controls.Markdown("Card 3").AddSkin(Skins.Card), s => s
.WithXs(12).WithSm(6).WithLg(3))
.WithView(Controls.Markdown("Card 4").AddSkin(Skins.Card), s => s
.WithXs(12).WithSm(6).WithLg(3))
Common Responsive Patterns
These three patterns cover the majority of real-world layouts.
Dashboard Cards
Items flow from a single column on phones to a four-column grid on wide monitors:
Controls.LayoutGrid
.WithSkin(skin => skin.WithSpacing(3))
.WithView(metricCard1, s => s.WithXs(12).WithSm(6).WithLg(3)) // 1 -> 2 -> 4 columns
.WithView(metricCard2, s => s.WithXs(12).WithSm(6).WithLg(3))
.WithView(metricCard3, s => s.WithXs(12).WithSm(6).WithLg(3))
.WithView(metricCard4, s => s.WithXs(12).WithSm(6).WithLg(3))
Sidebar + Content
A narrow navigation sidebar that stacks below the content on mobile:
Controls.LayoutGrid
.WithView(sidebar, s => s.WithXs(12).WithMd(3)) // Full width then sidebar
.WithView(content, s => s.WithXs(12).WithMd(9)) // Full width then main
Equal Thirds
Three equal columns that stack vertically on mobile and spread out on tablet and above:
Controls.LayoutGrid
.WithView(col1, s => s.WithXs(12).WithMd(4)) // Stack on mobile, thirds on tablet+
.WithView(col2, s => s.WithXs(12).WithMd(4))
.WithView(col3, s => s.WithXs(12).WithMd(4))
Tile Flow
A card catalog does not want breakpoints at all: it wants tiles of a minimum width that fill the row and wrap, however many there are and however wide the screen is. That is the grid's tile-flow mode — declare the minimum tile width and nothing else:
Controls.LayoutGrid
.WithMinTileWidth("180px")
.WithView(Controls.Markdown("**Tile 1**").AddSkin(Skins.Card))
.WithView(Controls.Markdown("**Tile 2**").AddSkin(Skins.Card))
.WithView(Controls.Markdown("**Tile 3**").AddSkin(Skins.Card))
.WithView(Controls.Markdown("**Tile 4**").AddSkin(Skins.Card))
.WithView(Controls.Markdown("**Tile 5**").AddSkin(Skins.Card))
Every child is a tile at least that wide. As many tiles as fit share a row in equal columns, and the
rest wrap onto the next row. A tile never grows wider than the container, so on a phone the grid is
one column. The per-item spans (WithXs … WithXxl) do not apply in this mode. The gap is
WithSpacing(n) × 4px, or 12px when no spacing is set.
Every renderer draws the same result. The web renderers (Blazor, React) use a CSS grid with
repeat(auto-fill, minmax(min(100%, w), 1fr)). React Native has no CSS grid, so it measures the
container and computes the column count. A view never writes that CSS itself: hand-rolled
display: grid styles render as an opaque style string on native, and they cannot see the list
problem below.
Tiles from a bound list
When the tiles come from data, put the mode on the bound list itself. The list's rendered items are then the tiles:
// ✅ every course row is its own tile
new ItemTemplateControl(card, new JsonPointerReference("")) { DataContext = coursesPointer }
.WithMinTileWidth("300px")
🚨 Never wrap a bound list in a grid container instead. A grid lays out its children, and a
bound list is ONE child, so the whole list becomes one grid cell and every tile stacks in a single
column. That is the exact failure a hand-rolled display: grid stack around an ItemTemplateControl
produces once lists render vertically by default.
| Method | On | Effect |
|---|---|---|
.WithMinTileWidth(w) |
LayoutGrid (shorthand for .WithSkin(s => s.WithMinTileWidth(w))) |
Static children become tiles of at least w |
.WithMinTileWidth(w) |
ItemTemplateControl |
Each bound row becomes a tile of at least w; Orientation/Wrap do not apply |
Grid Configuration
Control the container itself via .WithSkin():
| Method | Purpose | Values |
|---|---|---|
.WithSpacing(n) |
Gap between items | 1, 2, 3 (multiples of the base spacing unit) |
.WithJustify(j) |
Horizontal alignment of the row | "start", "center", "end" |
.WithMinTileWidth(w) |
Switches to tile flow (above) | a CSS length, e.g. "280px" |
Item Configuration
Each item added with .WithView(content, skin => ...) accepts these sizing methods:
| Method | Screen size | Activates at |
|---|---|---|
.WithXs(cols) |
Extra small | < 600 px |
.WithSm(cols) |
Small | ≥ 600 px |
.WithMd(cols) |
Medium | ≥ 960 px |
.WithLg(cols) |
Large | ≥ 1280 px |
.WithXl(cols) |
Extra large | ≥ 1920 px |
cols is an integer from 1 to 12. Items with no explicit size for a breakpoint inherit from the next smaller breakpoint that is set.
See Also
- Container Control — Stack, Tabs, Toolbar
- DataGrid — Tabular data display