diff --git a/crates/grida-canvas/src/htmlcss/collect.rs b/crates/grida-canvas/src/htmlcss/collect.rs
index cb29cea2a..723e1d3c5 100644
--- a/crates/grida-canvas/src/htmlcss/collect.rs
+++ b/crates/grida-canvas/src/htmlcss/collect.rs
@@ -432,8 +432,16 @@ fn collect_element_with_counter(
// Widgets with intrinsic sizes need their own Taffy node
// for sizing to work — don't flatten them into inline groups.
- let is_inline = child.display == types::Display::Inline
- || child.display == types::Display::InlineBlock;
+ // Same for inline-block with explicit sizing: its width/height
+ // would be lost inside the inline path. Layout later emulates
+ // inline-block flow by mapping the parent to Taffy flex-wrap
+ // when all of its element children are inline-block.
+ let inline_block_with_size = child.display == types::Display::InlineBlock
+ && (child.width != types::CssLength::Auto
+ || child.height != types::CssLength::Auto);
+ let is_inline = (child.display == types::Display::Inline
+ || child.display == types::Display::InlineBlock)
+ && !inline_block_with_size;
if is_inline && !child.widget.is_widget() && child.replaced.is_none() {
collect_inline_items(&child, &mut pending_inline);
} else {
diff --git a/crates/grida-canvas/src/htmlcss/layout.rs b/crates/grida-canvas/src/htmlcss/layout.rs
index 7c03e12a3..c99967c9a 100644
--- a/crates/grida-canvas/src/htmlcss/layout.rs
+++ b/crates/grida-canvas/src/htmlcss/layout.rs
@@ -126,6 +126,19 @@ fn build_taffy_node(
apply_replaced_intrinsic_size(&mut style, replaced, images);
}
+ // Emulate inline-block sibling flow via Taffy flex-wrap. Taffy has no
+ // inline formatting context, so a block container holding only
+ // inline-block siblings would otherwise stack them vertically. Only
+ // apply when ≥2 inline-block element children exist and no text or
+ // non-inline-block elements would be misrouted through flex.
+ if style.display == taffy::Display::Block && should_emulate_inline_block_container(el) {
+ style.display = taffy::Display::Flex;
+ style.flex_wrap = taffy::FlexWrap::Wrap;
+ // Override default `stretch` so inline-blocks keep their
+ // own block-size instead of filling the container's line height.
+ style.align_items = Some(taffy::AlignItems::Start);
+ }
+
// Build child nodes
let mut child_ids: Vec = Vec::new();
@@ -176,6 +189,26 @@ fn build_taffy_node(
taffy.new_with_children(style, &child_ids).unwrap()
}
+/// Returns true when `el` should lay out its children as a horizontal
+/// flex-wrap row to emulate inline-block flow. Only safe when the
+/// container holds ≥2 inline-block element siblings and no text or
+/// non-inline-block element children (those would require a real inline
+/// formatting context to mix with inline-blocks correctly).
+fn should_emulate_inline_block_container(el: &StyledElement) -> bool {
+ let mut inline_block_count = 0usize;
+ for child in &el.children {
+ match child {
+ StyledNode::Element(child_el) => match child_el.display {
+ types::Display::InlineBlock => inline_block_count += 1,
+ types::Display::None => {}
+ _ => return false,
+ },
+ StyledNode::Text(_) | StyledNode::InlineGroup(_) => return false,
+ }
+ }
+ inline_block_count >= 2
+}
+
/// Taffy context for text/inline leaf nodes. Stores inline items so the
/// measure function can build a Skia Paragraph with placeholders at any
/// available width.
diff --git a/fixtures/test-html/L0/layout-display-inline-block.html b/fixtures/test-html/L0/layout-display-inline-block.html
new file mode 100644
index 000000000..7bb679ed5
--- /dev/null
+++ b/fixtures/test-html/L0/layout-display-inline-block.html
@@ -0,0 +1,51 @@
+
+
+
+
+ Layout: display: inline-block
+
+
+
+
+
+
diff --git a/fixtures/test-html/suites/L0.exact.json b/fixtures/test-html/suites/L0.exact.json
index 2e21f5434..5716626e4 100644
--- a/fixtures/test-html/suites/L0.exact.json
+++ b/fixtures/test-html/suites/L0.exact.json
@@ -85,6 +85,7 @@
{ "path": "../L0/paint-border-style-dashed.html" },
{ "path": "../L0/paint-filter-drop-shadow.html" },
{ "path": "../L0/paint-transform-matrix.html" },
- { "path": "../L0/paint-filter-blur.html" }
+ { "path": "../L0/paint-filter-blur.html" },
+ { "path": "../L0/layout-display-inline-block.html" }
]
}