From db5607395608ffcd727bc5c15469abd826d9ce41 Mon Sep 17 00:00:00 2001
From: Leo Farias
Date: Tue, 18 Aug 2026 19:32:31 -0400
Subject: [PATCH 1/7] feat(mix): add content-sized GridTrack.auto() rows
GridBox could not express a grid whose rows size to their tallest child.
The only workaround, autoRows(GridTrack.fixed(n)), silently truncated any
child taller than the declared track, so a scroll view of unknown-height
content had no correct option.
Add GridTrack.auto(), valid in rows and autoRows, and default an omitted
autoRows to it so implicit rows match CSS's grid-auto-rows: auto. Columns
still reject auto; content-sized columns need a separate two-axis design.
RenderMixGrid keeps the one-pass path for fixed/fr grids. When any
effective row is auto, children in those rows are measured at their
resolved column width with a loose height, each row takes its tallest
measurement, and the final pass lays every child into the stretched cell.
Live layout, dry layout, and vertical intrinsics share that geometry
through Flutter's ChildLayouter helpers, following RenderTable's pattern.
The wire format gains a fieldless {"type":"auto"} track as an additive v1
discriminator, accepted on rows and autoRows only.
BREAKING CHANGE: a GridBox that needed more rows than it declared used to
throw; it now lays out with content-sized implicit rows. This applies both
when no rows are declared and when explicit rows undershoot the child
count.
---
packages/mix/CHANGELOG.md | 17 +
packages/mix/README.md | 8 +-
packages/mix/doc/grid-layout.md | 85 +-
packages/mix/example/.gitignore | 1 +
packages/mix/example/README.md | 4 +-
packages/mix/example/lib/grid_example.dart | 37 +-
.../test/goldens/grid_catalog_compact.png | Bin 63179 -> 74177 bytes
.../test/goldens/grid_catalog_wide.png | Bin 45446 -> 61593 bytes
.../example/test/grid_box_example_test.dart | 40 +
packages/mix/lib/src/layout/grid_box.dart | 24 +-
.../mix/lib/src/layout/grid_box_spec.dart | 46 +-
packages/mix/lib/src/layout/grid_track.dart | 44 +-
.../src/layout/internal/grid_geometry.dart | 7 +-
.../src/layout/internal/grid_validation.dart | 3 +-
packages/mix/lib/src/layout/render_grid.dart | 277 ++++--
.../mix/test/src/layout/grid_box_test.dart | 866 ++++++++++++++++--
.../src/layout/grid_layout_property_test.dart | 79 +-
.../test/src/layout/grid_public_api_test.dart | 30 +
packages/mix_protocol/CHANGELOG.md | 2 +
packages/mix_protocol/GUIDE.md | 2 +-
packages/mix_protocol/WIRE_CONTRACT.md | 6 +-
.../lib/src/schema/grid_box_styler_codec.dart | 30 +-
.../lib/src/schema/wire_discriminators.dart | 1 +
.../src/tokens/token_reference_walker.dart | 2 +
.../test/grid_box_styler_codec_test.dart | 100 ++
.../test/schema_export_golden_test.dart | 31 +-
.../test/styler_round_trip_test.dart | 2 +-
skills/mix/SKILL.md | 2 +-
skills/mix/references/fluent-api.md | 2 +-
skills/mix/references/layout.md | 36 +-
skills/mix/references/styler-api-policy.md | 2 +-
31 files changed, 1580 insertions(+), 206 deletions(-)
create mode 100644 packages/mix/example/.gitignore
diff --git a/packages/mix/CHANGELOG.md b/packages/mix/CHANGELOG.md
index c16eb1aba..d967cc2d9 100644
--- a/packages/mix/CHANGELOG.md
+++ b/packages/mix/CHANGELOG.md
@@ -2,6 +2,10 @@
### New features
+- **GridTrack.auto():** Vertical auto tracks for explicit `rows` and
+ `autoRows`. `auto` is vertical-only; columns still reject it. Existing
+ explicit fixed/`fr` layouts keep a single child layout per pass. Auto-row
+ children may be measured and then stretched.
- **Generated Styler field metadata:** Every generated Styler now exposes its
complete source-field inventory through
`StylerFieldMetadata.$stylerFieldNames`, allowing schema tooling to validate
@@ -9,6 +13,19 @@
previously generated Stylers do not implement this capability until they opt
in or are regenerated.
+### Breaking changes
+
+- **Omitted GridBox autoRows no longer throws:** Previously, children that
+ needed more rows than were declared required an explicit `autoRows` track
+ or the grid threw. Omitted `autoRows` now defaults to `GridTrack.auto()`,
+ so implicit rows size to their tallest child — both when no rows are
+ declared and when some explicit rows exist but are not enough. Fractional
+ rows still require a bounded height. Fixed tracks remain hard constraints.
+ Because the new default opts a Grid into the measure-then-stretch pass,
+ nesting compounds it: a leaf inside one auto Grid is laid out twice, and
+ inside three nested auto Grids eight times. Give inner levels fixed rows
+ once their heights are known.
+
## 2.2.0-beta.4
### Fixes
diff --git a/packages/mix/README.md b/packages/mix/README.md
index 9f6bd176b..89c6aefb6 100644
--- a/packages/mix/README.md
+++ b/packages/mix/README.md
@@ -165,14 +165,14 @@ the runnable [WrapBox example](example/README.md).
### Grid Layouts
-`GridBox` uses fixed and fractional tracks and can adapt to the width offered by
-its own parent. Dot shorthand keeps nested track and breakpoint declarations
-compact:
+`GridBox` uses fixed and fractional columns plus vertical auto rows, and can
+adapt to the width offered by its own parent. Dot shorthand keeps nested track
+and breakpoint declarations compact. Omit `autoRows` (or set `.autoRows(.auto())`)
+when row height should follow the tallest child:
```dart
final GridBoxStyler dashboardGrid = .equalColumns(3)
.gap(16)
- .autoRows(.fixed(220))
.onConstraints(
.maxWidth(720),
.equalColumns(1).gap(12),
diff --git a/packages/mix/doc/grid-layout.md b/packages/mix/doc/grid-layout.md
index 21bf24126..1c20d925f 100644
--- a/packages/mix/doc/grid-layout.md
+++ b/packages/mix/doc/grid-layout.md
@@ -1,8 +1,9 @@
# Grid layout
-`GridBox` arranges children in row-major order using fixed and fractional
-tracks. Its style supports ordinary Mix variants and modifiers, plus
-`onConstraints` for layout decisions based on the Grid's own available space.
+`GridBox` arranges children in row-major order using fixed, fractional, and
+vertical auto tracks. Its style supports ordinary Mix variants and modifiers,
+plus `onConstraints` for layout decisions based on the Grid's own available
+space.
## Basic usage
@@ -12,16 +13,19 @@ entry point, then chain the remaining geometry:
```dart
final GridBoxStyler style = .equalColumns(3)
.gap(16)
- .autoRows(.fixed(160));
+ .autoRows(.auto());
GridBox(style: style, children: cards);
```
-`GridTrack.fixed(size)` keeps its logical-pixel size. `GridTrack.fr(fraction)`
-receives that fraction of the free space left after fixed tracks and gaps.
-For example, with 300 pixels of remaining space, `[.fr(2), .fr(1)]` produces
-tracks of 200 and 100 pixels. Fractional tracks require a bounded parent extent
-on their axis. Use `columns` directly when tracks are intentionally mixed:
+`GridTrack.fixed(size)` keeps its logical-pixel size and is a hard constraint:
+children in a fixed row are stretched or clipped to that height. `GridTrack.fr(fraction)`
+receives that fraction of the free space left after fixed tracks, auto tracks,
+and gaps. For example, with 300 pixels of remaining space, `[.fr(2), .fr(1)]`
+produces tracks of 200 and 100 pixels. Fractional tracks require a bounded
+parent extent on their axis. `GridTrack.auto()` is vertical-only: the row
+sizes to its tallest child's natural height at the resolved column width.
+Use `columns` directly when tracks are intentionally mixed:
```dart
final GridBoxStyler sidebarAndContent = .columns([
@@ -38,7 +42,6 @@ the bounded maximum size offered to this Grid:
```dart
final GridBoxStyler cardGrid = .equalColumns(3)
.gap(16)
- .autoRows(.fixed(220))
.onConstraints(
.maxWidth(760),
.equalColumns(2).gap(12),
@@ -69,22 +72,41 @@ Two GridBoxes on the same screen can therefore select different
Children fill columns from left to right, then continue on the next row.
Provide explicit `rows` for known row geometry, or `autoRows` for each repeated
-row needed beyond the explicit list:
+row needed beyond the explicit list. Omitted `autoRows` defaults to
+`GridTrack.auto()`, so a two-column Grid with no row declaration sizes each
+implicit row to its tallest child:
```dart
-final GridBoxStyler gallery = .equalColumns(2).rows([
+final GridBoxStyler gallery = .equalColumns(2).gap(12);
+
+final GridBoxStyler mixed = .equalColumns(2).rows([
.fixed(180),
-]).autoRows(.fixed(180)).columnGap(12).rowGap(12);
+]).autoRows(.auto()).columnGap(12).rowGap(12);
```
-With five children and two columns, the example needs three rows: the first
-uses the explicit 180-pixel row and the next two repeat `autoRows`. When
-`rows` is empty, every required row uses `autoRows`.
+With five children and two columns, `mixed` needs three rows: the first uses
+the explicit 180-pixel row and the next two repeat `autoRows`. When `rows` is
+empty, every required row uses `autoRows` (or `auto` when it is omitted).
+
+Use `GridTrack.auto()` — or omit `autoRows` — in a vertical
+`SingleChildScrollView` when child heights are unknown. Auto-row children are
+measured at their column width, then laid out again into the stretched cell so
+shorter siblings fill the row. That extra measure pass runs only for children
+in auto rows; explicit fixed/`fr` Grids still lay each child out once.
+
+Nesting compounds that cost. Each auto-row level measures its subtree and then
+lays it out again, so the passes multiply rather than add: a leaf inside one
+auto Grid is laid out twice, and inside three nested auto Grids it is laid out
+eight times. For deep hierarchies, prefer a single Grid, or give the inner
+levels fixed rows once their heights are known.
-If children require more rows than declared and `autoRows` is absent, GridBox
-reports an actionable layout error rather than guessing a content-sizing rule.
-Use fixed rows on an unbounded vertical axis such as `SingleChildScrollView`.
-Fractional rows and fractional `autoRows` require bounded height.
+Fixed rows stay hard heights. Fractional rows and fractional `autoRows` still
+require bounded height. Auto rows require children with a finite natural
+height; `Expanded`, `Spacer`, or another expanding child inside an auto row
+in a scroll view is an error.
+
+`GridTrack.auto()` is rejected on `columns`. Content-sized columns need a
+separate two-axis design and are not part of this API.
## Design tokens
@@ -151,6 +173,7 @@ Animation compatibility is positional:
- fixed tracks interpolate with fixed tracks;
- fractional tracks interpolate with fractional tracks;
+- auto tracks stay compatible with auto tracks;
- the two track lists must keep the same length and track kinds;
- compatible `autoRows`, `columnGap`, and `rowGap` values interpolate too.
@@ -179,11 +202,27 @@ contained:
final GridBoxStyler clipped = .clipBehavior(.hardEdge);
```
+Auto rows are diagnosed the same way. Measured row heights that add up to more
+than a bounded parent offers overflow the Grid's box and report the same
+indicator, so putting tall content in a bounded parent is still visible rather
+than silent.
+
+A child that outgrows its own cell is not diagnosed. The Grid compares its
+total track extent against the space the parent offered; it never asks whether
+an individual child fits the cell it was given. A fixed track is therefore a
+hard constraint in exactly the way a tight `SizedBox` is, and content taller
+than that track is constrained without a warning. This is deliberate: probing
+every fixed cell for its natural height would cost a speculative measure pass
+per child and would break children that require a bounded height. Use `auto`
+rows whenever the content height is unknown, and keep `.fixed(...)` for cells
+where constraining the child is the intent.
+
## Current track model
-GridBox intentionally supports fixed and fractional tracks with row-major
-auto-placement. Content-sized tracks, spans, named areas, direction-aware
-placement, and baseline alignment are not part of the current API.
+GridBox supports fixed and fractional tracks on both axes and content-sized
+`auto` tracks on rows only, with row-major auto-placement. Content-sized
+columns, spans, named areas, direction-aware placement, and baseline alignment
+are not part of the current API.
Run the card, dashboard, gallery, and animation examples from
`packages/mix/example`:
diff --git a/packages/mix/example/.gitignore b/packages/mix/example/.gitignore
new file mode 100644
index 000000000..02a517ea2
--- /dev/null
+++ b/packages/mix/example/.gitignore
@@ -0,0 +1 @@
+test/failures/
diff --git a/packages/mix/example/README.md b/packages/mix/example/README.md
index e4ce2fb4c..5486ec7bf 100644
--- a/packages/mix/example/README.md
+++ b/packages/mix/example/README.md
@@ -24,7 +24,6 @@ rather than the viewport:
```dart
final GridBoxStyler cardGrid = .equalColumns(3)
.gap(16)
- .autoRows(.fixed(220))
.onConstraints(
.maxWidth(760),
.equalColumns(2).gap(12),
@@ -45,7 +44,8 @@ columns to one.
-The same track model handles repeated product cards and a denser media gallery:
+The card catalog omits `autoRows` so implicit rows size to unequal copy. The
+media gallery keeps fixed automatic rows for a dense, even tile height:
diff --git a/packages/mix/example/lib/grid_example.dart b/packages/mix/example/lib/grid_example.dart
index 7c8dcf73b..9724888d6 100644
--- a/packages/mix/example/lib/grid_example.dart
+++ b/packages/mix/example/lib/grid_example.dart
@@ -444,50 +444,58 @@ class CatalogGridPreview extends StatelessWidget {
Widget build(BuildContext context) {
final GridBoxStyler style = .equalColumns(3)
.gap(16)
- .autoRows(.fixed(220))
.onConstraints(.maxWidth(760), .equalColumns(2).gap(12))
- .onConstraints(
- .maxWidth(520),
- .equalColumns(1).gap(10).autoRows(.fixed(190)),
- );
+ .onConstraints(.maxWidth(520), .equalColumns(1).gap(10));
return GridBox(
key: const Key('catalog-grid'),
style: style,
children: const [
_ProductCard(
+ key: Key('catalog-card-0'),
'Canvas tote',
r'$48',
+ 'A roomy everyday bag.',
Icons.shopping_bag_rounded,
Color(0xFFE9E6FF),
),
_ProductCard(
+ key: Key('catalog-card-1'),
'Desk lamp',
r'$72',
+ 'Adjustable arm and a warm-dim LED. Built for late editing sessions when the rest of the room has gone dark.',
Icons.light_rounded,
Color(0xFFFFE9D8),
),
_ProductCard(
+ key: Key('catalog-card-2'),
'Travel mug',
r'$32',
+ 'Keeps coffee hot through a standup.',
Icons.coffee_rounded,
Color(0xFFDDF4EE),
),
_ProductCard(
+ key: Key('catalog-card-3'),
'Studio clock',
r'$64',
+ 'Quiet sweep, high-contrast face.',
Icons.schedule_rounded,
Color(0xFFDDEBFA),
),
_ProductCard(
+ key: Key('catalog-card-4'),
'Wool throw',
r'$96',
+ 'Heavyweight merino for the couch.',
Icons.bed_rounded,
Color(0xFFF6E2EB),
),
_ProductCard(
+ key: Key('catalog-card-5'),
'Plant stand',
r'$58',
+ 'Three-tier oak riser.',
Icons.eco_rounded,
Color(0xFFE4F2D8),
),
@@ -497,10 +505,18 @@ class CatalogGridPreview extends StatelessWidget {
}
class _ProductCard extends StatelessWidget {
- const _ProductCard(this.name, this.price, this.icon, this.tint);
+ const _ProductCard(
+ this.name,
+ this.price,
+ this.blurb,
+ this.icon,
+ this.tint, {
+ super.key,
+ });
final String name;
final String price;
+ final String blurb;
final IconData icon;
final Color tint;
@@ -508,6 +524,7 @@ class _ProductCard extends StatelessWidget {
Widget build(BuildContext context) {
return _CardSurface(
child: Column(
+ mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
@@ -519,15 +536,15 @@ class _ProductCard extends StatelessWidget {
),
child: Icon(icon, color: const Color(0xFF3C3D4A)),
),
- const Spacer(),
+ const SizedBox(height: 16),
Text(
name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
),
const SizedBox(height: 4),
- const Text(
- 'Essential collection',
- style: TextStyle(color: Color(0xFF77798A), fontSize: 12),
+ Text(
+ blurb,
+ style: const TextStyle(color: Color(0xFF77798A), fontSize: 12),
),
const SizedBox(height: 12),
Row(
diff --git a/packages/mix/example/test/goldens/grid_catalog_compact.png b/packages/mix/example/test/goldens/grid_catalog_compact.png
index 880faeabece3d2499de43bf7f5944449429856ca..a50877f4a2cd80b0094e60a3ffbb5ba8e68e32f0 100644
GIT binary patch
literal 74177
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