From 5d774a4c179993e3fb1f24a733b63ec72b0e2259 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Tue, 18 Aug 2026 23:40:37 +0000 Subject: [PATCH 1/2] Grid: exclude items with auto block-axis margins from baseline alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per css-align-3 §9.5, a box with an auto margin in the relevant axis does not participate in baseline alignment. Matches the flexbox implementation's participates_in_baseline_alignment check. --- src/compute/grid/mod.rs | 18 ++++--- src/compute/grid/track_sizing.rs | 26 ++++++---- src/compute/grid/types/grid_item.rs | 8 ++++ ...lign_items_baseline_child_auto_margin.html | 22 +++++++++ ..._child_auto_margin_container_baseline.html | 22 +++++++++ ...line_child_auto_margin__border_box_ltr.xml | 21 ++++++++ ...line_child_auto_margin__border_box_rtl.xml | 21 ++++++++ ...ine_child_auto_margin__content_box_ltr.xml | 21 ++++++++ ...ine_child_auto_margin__content_box_rtl.xml | 21 ++++++++ ...gin_container_baseline__border_box_ltr.xml | 21 ++++++++ ...gin_container_baseline__border_box_rtl.xml | 21 ++++++++ ...in_container_baseline__content_box_ltr.xml | 21 ++++++++ ...in_container_baseline__content_box_rtl.xml | 21 ++++++++ tests/xml/mod.rs | 48 +++++++++++++++++++ 14 files changed, 294 insertions(+), 18 deletions(-) create mode 100644 test_fixtures/grid/grid_align_items_baseline_child_auto_margin.html create mode 100644 test_fixtures/grid/grid_baseline_child_auto_margin_container_baseline.html create mode 100644 tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_ltr.xml create mode 100644 tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_rtl.xml create mode 100644 tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_ltr.xml create mode 100644 tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_rtl.xml create mode 100644 tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_ltr.xml create mode 100644 tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_rtl.xml create mode 100644 tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_ltr.xml create mode 100644 tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_rtl.xml diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs index 2f4b9280b..0bd31f460 100644 --- a/src/compute/grid/mod.rs +++ b/src/compute/grid/mod.rs @@ -2,7 +2,7 @@ //! use crate::geometry::{AbsoluteAxis, AbstractAxis, InBothAbsAxis}; use crate::geometry::{Line, Rect, Size}; -use crate::style::{AlignItems, AlignSelf, AvailableSpace, Overflow, Position}; +use crate::style::{AlignItems, AvailableSpace, Overflow, Position}; use crate::tree::{Baselines, Layout, LayoutInput, LayoutOutput, LayoutPartialTreeExt, NodeId, RunMode, SizingMode}; use crate::util::debug::debug_log; use crate::util::sys::{f32_max, f32_min, GridTrackVec, Vec}; @@ -305,7 +305,7 @@ pub fn compute_grid_layout( determine_if_item_crosses_flexible_or_intrinsic_tracks(&mut items, &columns, &rows); // Determine if the grid has any baseline aligned items - let has_baseline_aligned_item = items.iter().any(|item| item.align_self == AlignSelf::BASELINE); + let has_baseline_aligned_item = items.iter().any(|item| item.participates_in_baseline_alignment()); // Run track sizing algorithm for Inline axis track_sizing_algorithm( @@ -795,14 +795,12 @@ pub fn compute_grid_layout( // Create a slice of all of the items start in this row (taking advantage of the fact that we have just sorted the array) let first_row_items = &items[0..].split(|item| item.row_indexes.start != first_row).next().unwrap(); - // Check if any items in *this row* are baseline aligned - let row_has_baseline_item = first_row_items.iter().any(|item| item.align_self == AlignSelf::BASELINE); - - let item = if row_has_baseline_item { - first_row_items.iter().find(|item| item.align_self == AlignSelf::BASELINE).unwrap() - } else { - &first_row_items[0] - }; + // Check if any items in *this row* participate in baseline alignment + // (items with an auto block-axis margin do not participate: https://www.w3.org/TR/css-align-3/#baseline-align-self) + let item = first_row_items + .iter() + .find(|item| item.participates_in_baseline_alignment()) + .unwrap_or(&first_row_items[0]); item.y_position + item.baseline.unwrap_or(item.height) }; diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs index 5e5f4bddb..0184dcb44 100644 --- a/src/compute/grid/track_sizing.rs +++ b/src/compute/grid/track_sizing.rs @@ -2,7 +2,7 @@ //! use super::types::{GridItem, GridTrack, TrackCounts}; use crate::geometry::{AbstractAxis, Line, Size}; -use crate::style::{AlignContent, AlignContentKeyword, AlignSelf, AvailableSpace}; +use crate::style::{AlignContent, AlignContentKeyword, AvailableSpace}; use crate::style_helpers::TaffyMinContent; use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, SizingMode}; use crate::util::sys::{f32_max, f32_min, Vec}; @@ -493,13 +493,17 @@ fn resolve_item_baselines( // Count how many items in *this row* are baseline aligned // If a row has one or zero items participating in baseline alignment then baseline alignment is a no-op // for those items and we skip further computations for that row - let row_baseline_item_count = row_items.iter().filter(|item| item.align_self == AlignSelf::BASELINE).count(); + let row_baseline_item_count = row_items.iter().filter(|item| item.participates_in_baseline_alignment()).count(); if row_baseline_item_count <= 1 { continue; } - // Compute the baselines of all items in the row + // Compute the baselines of all items in the row participating in baseline alignment for item in row_items.iter_mut() { + if !item.participates_in_baseline_alignment() { + continue; + } + let measured_size_and_baselines = tree.perform_child_layout( item.node, Size::NONE, @@ -526,13 +530,19 @@ fn resolve_item_baselines( ); } - // Compute the max baseline of all items in the row - let row_max_baseline = - row_items.iter().map(|item| item.baseline.unwrap_or(0.0)).max_by(|a, b| a.total_cmp(b)).unwrap(); + // Compute the max baseline of all items in the row participating in baseline alignment + let row_max_baseline = row_items + .iter() + .filter(|item| item.participates_in_baseline_alignment()) + .map(|item| item.baseline.unwrap_or(0.0)) + .max_by(|a, b| a.total_cmp(b)) + .unwrap(); - // Compute the baseline shim for each item in the row + // Compute the baseline shim for each item in the row participating in baseline alignment for item in row_items.iter_mut() { - item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0); + if item.participates_in_baseline_alignment() { + item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0); + } } } } diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs index f3b51db19..dda398531 100644 --- a/src/compute/grid/types/grid_item.rs +++ b/src/compute/grid/types/grid_item.rs @@ -137,6 +137,14 @@ impl GridItem { } } + /// Returns true if the item participates in baseline alignment: it has `align-self: baseline` + /// and neither of its block-axis margins are `auto`. + /// See + #[inline(always)] + pub fn participates_in_baseline_alignment(&self) -> bool { + self.align_self == AlignSelf::BASELINE && !self.margin.top.is_auto() && !self.margin.bottom.is_auto() + } + /// This item's placement in the specified axis in OriginZero coordinates pub fn placement(&self, axis: AbstractAxis) -> Line { match axis { diff --git a/test_fixtures/grid/grid_align_items_baseline_child_auto_margin.html b/test_fixtures/grid/grid_align_items_baseline_child_auto_margin.html new file mode 100644 index 000000000..28eaec070 --- /dev/null +++ b/test_fixtures/grid/grid_align_items_baseline_child_auto_margin.html @@ -0,0 +1,22 @@ + + + + + + + An item with an auto block-axis margin does not participate in baseline alignment and is + not baseline-shimmed: it is bottom-aligned by its auto margin instead + + + + +
+
+
+
+
+
+
+ + + diff --git a/test_fixtures/grid/grid_baseline_child_auto_margin_container_baseline.html b/test_fixtures/grid/grid_baseline_child_auto_margin_container_baseline.html new file mode 100644 index 000000000..8a5e3031f --- /dev/null +++ b/test_fixtures/grid/grid_baseline_child_auto_margin_container_baseline.html @@ -0,0 +1,22 @@ + + + + + + + An item with an auto block-axis margin does not participate in baseline alignment, so the + grid container's baseline is generated from the row's first item instead + + + + +
+
+
+
+
+
+
+ + + diff --git a/tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_ltr.xml b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_ltr.xml new file mode 100644 index 000000000..58d0ea523 --- /dev/null +++ b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_ltr.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_rtl.xml b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_rtl.xml new file mode 100644 index 000000000..7fcb879f0 --- /dev/null +++ b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__border_box_rtl.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_ltr.xml b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_ltr.xml new file mode 100644 index 000000000..d26b93400 --- /dev/null +++ b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_ltr.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_rtl.xml b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_rtl.xml new file mode 100644 index 000000000..6235e0830 --- /dev/null +++ b/tests/xml/grid/grid_align_items_baseline_child_auto_margin__content_box_rtl.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_ltr.xml b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_ltr.xml new file mode 100644 index 000000000..695620e2e --- /dev/null +++ b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_ltr.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_rtl.xml b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_rtl.xml new file mode 100644 index 000000000..eeba37cff --- /dev/null +++ b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__border_box_rtl.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_ltr.xml b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_ltr.xml new file mode 100644 index 000000000..373a409da --- /dev/null +++ b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_ltr.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_rtl.xml b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_rtl.xml new file mode 100644 index 000000000..15c33760c --- /dev/null +++ b/tests/xml/grid/grid_baseline_child_auto_margin_container_baseline__content_box_rtl.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/xml/mod.rs b/tests/xml/mod.rs index 688ef17b4..486e44517 100644 --- a/tests/xml/mod.rs +++ b/tests/xml/mod.rs @@ -20649,6 +20649,30 @@ mod grid { crate::run_xml_test("grid", "grid_align_items_baseline_child__content_box_rtl"); } + #[cfg(feature = "grid")] + #[test] + fn grid_align_items_baseline_child_auto_margin__border_box_ltr() { + crate::run_xml_test("grid", "grid_align_items_baseline_child_auto_margin__border_box_ltr"); + } + + #[cfg(feature = "grid")] + #[test] + fn grid_align_items_baseline_child_auto_margin__content_box_ltr() { + crate::run_xml_test("grid", "grid_align_items_baseline_child_auto_margin__content_box_ltr"); + } + + #[cfg(feature = "grid")] + #[test] + fn grid_align_items_baseline_child_auto_margin__border_box_rtl() { + crate::run_xml_test("grid", "grid_align_items_baseline_child_auto_margin__border_box_rtl"); + } + + #[cfg(feature = "grid")] + #[test] + fn grid_align_items_baseline_child_auto_margin__content_box_rtl() { + crate::run_xml_test("grid", "grid_align_items_baseline_child_auto_margin__content_box_rtl"); + } + #[cfg(feature = "grid")] #[test] fn grid_align_items_baseline_child_margin__border_box_ltr() { @@ -21849,6 +21873,30 @@ mod grid { crate::run_xml_test("grid", "grid_available_space_smaller_than_min_content__content_box_rtl"); } + #[cfg(feature = "grid")] + #[test] + fn grid_baseline_child_auto_margin_container_baseline__border_box_ltr() { + crate::run_xml_test("grid", "grid_baseline_child_auto_margin_container_baseline__border_box_ltr"); + } + + #[cfg(feature = "grid")] + #[test] + fn grid_baseline_child_auto_margin_container_baseline__content_box_ltr() { + crate::run_xml_test("grid", "grid_baseline_child_auto_margin_container_baseline__content_box_ltr"); + } + + #[cfg(feature = "grid")] + #[test] + fn grid_baseline_child_auto_margin_container_baseline__border_box_rtl() { + crate::run_xml_test("grid", "grid_baseline_child_auto_margin_container_baseline__border_box_rtl"); + } + + #[cfg(feature = "grid")] + #[test] + fn grid_baseline_child_auto_margin_container_baseline__content_box_rtl() { + crate::run_xml_test("grid", "grid_baseline_child_auto_margin_container_baseline__content_box_rtl"); + } + #[cfg(feature = "grid")] #[test] fn grid_basic__border_box_ltr() { From 5c84942fea9d8f759a04992f145e440cdb58b1c5 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Tue, 18 Aug 2026 23:41:07 +0000 Subject: [PATCH 2/2] Add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b799d0ae..563dbbef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ ### Fixed +- Grid: items with an `auto` block-axis margin no longer participate in baseline alignment, per [CSS Align §9.5](https://www.w3.org/TR/css-align-3/#baseline-align-self). Such items are no longer baseline-shimmed (their auto margin aligns them instead), and they are no longer selected as the item the grid container's own first baseline is generated from, matching the existing flexbox behaviour + - Flexbox: a flex item's cross-axis size is now only treated as definite (for resolving percentage sizes of its descendants) when the item is stretched or its cross size style resolves to a definite size, per [CSS Flexbox §4.5](https://www.w3.org/TR/css-flexbox-1/#definite-sizes). Previously content-derived cross sizes of non-stretched items were incorrectly used as percentage resolution bases - Block: a container's `min-height` alone no longer acts as the resolution basis for the percentage heights of its children when the container's own height is indefinite. Per [CSS 2 §10.5](https://www.w3.org/TR/CSS22/visudet.html#the-height-property), such percentages resolve as `auto`