@@ -1571,6 +1571,18 @@ fn strip_string_quotes(val: &str) -> &str {
1574+// True if any direct child is `@in-front` or `@behind`. Such children render
1575+// as absolutely positioned overlays, so the parent automatically becomes a
1576+// positioning context (position:relative + isolation:isolate).
1577+fn has_overlay_children(elem: &Element) -> bool {
1578+ elem.children.iter().any(|child| {
1581+ Node::Element(e) if matches!(e.kind, ElementKind::InFront | ElementKind::Behind)
1574fn generate_element(
1586fn generate_element(
1575 elem: &Element,
1587 elem: &Element,
1576 parent_kind: Option<&ElementKind>,
1588 parent_kind: Option<&ElementKind>,
@@ -1641,7 +1653,7 @@ fn generate_element(
1642 // @breadcrumb generates semantic <nav aria-label="breadcrumb"><ol>...</ol></nav>
1654 // @breadcrumb generates semantic <nav aria-label="breadcrumb"><ol>...</ol></nav>
1643 if elem.kind == ElementKind::Breadcrumb {
1655 if elem.kind == ElementKind::Breadcrumb {
1644− let gen_class = compute_class(&elem.attrs, &elem.kind, parent_kind, styles);
1656+ let gen_class = compute_class(&elem.attrs, &elem.kind, parent_kind, styles, false);
1645 let (id, user_class) = extract_id_class(&elem.attrs);
1657 let (id, user_class) = extract_id_class(&elem.attrs);
1646 out.push_str(&ctx.indent());
1658 out.push_str(&ctx.indent());
1647 out.push_str("<nav aria-label=\"breadcrumb\"");
1659 out.push_str("<nav aria-label=\"breadcrumb\"");
@@ -1696,7 +1708,9 @@ fn generate_element(
1696 | ElementKind::Column
1708 | ElementKind::Column
1697 | ElementKind::El
1709 | ElementKind::El
1698 | ElementKind::Grid
1710 | ElementKind::Grid
1699− | ElementKind::Stack => "div",
1711+ | ElementKind::Stack
1712+ | ElementKind::InFront
1713+ | ElementKind::Behind => "div",
1700 ElementKind::Text => "span",
1714 ElementKind::Text => "span",
1701 ElementKind::Paragraph => "p",
1715 ElementKind::Paragraph => "p",
1702 ElementKind::Link => "a",
1716 ElementKind::Link => "a",
@@ -1853,6 +1867,8 @@ fn generate_element(
1853 ElementKind::Noscript => "noscript",
1867 ElementKind::Noscript => "noscript",
1854 ElementKind::Address => "address",
1868 ElementKind::Address => "address",
1855 ElementKind::Search => "search",
1869 ElementKind::Search => "search",
1870+ ElementKind::InFront => "in-front",
1871+ ElementKind::Behind => "behind",
1856 ElementKind::H1 => "h1",
1872 ElementKind::H1 => "h1",
1857 ElementKind::H2 => "h2",
1873 ElementKind::H2 => "h2",
1858 ElementKind::H3 => "h3",
1874 ElementKind::H3 => "h3",
@@ -1875,7 +1891,14 @@ fn generate_element(
1877 // Compute CSS for each state and get a class name
1893 // Compute CSS for each state and get a class name
1894+ let overlay_children = has_overlay_children(elem);
1878− let gen_class = compute_class(&elem.attrs, &elem.kind, parent_kind, styles);
1895+ let gen_class = compute_class(
1879 let (id, user_class) = extract_id_class(&elem.attrs);
1902 let (id, user_class) = extract_id_class(&elem.attrs);
1881 if ctx.dev && elem.line_num > 0 {
1904 if ctx.dev && elem.line_num > 0 {
@@ -1953,7 +1976,7 @@ fn generate_element(
1953 // Critical CSS: inline styles directly instead of using a class
1976 // Critical CSS: inline styles directly instead of using a class
1954 let is_critical = elem.attrs.iter().any(|a| a.key == "critical");
1977 let is_critical = elem.attrs.iter().any(|a| a.key == "critical");
1955 if is_critical {
1978 if is_critical {
1956− let inline_css = attrs_to_css(&elem.attrs, "", &elem.kind, parent_kind);
1979+ let inline_css = attrs_to_css(&elem.attrs, "", &elem.kind, parent_kind, overlay_children);
1957 if !inline_css.is_empty() {
1980 if !inline_css.is_empty() {
1958 out.push_str(" style=\"");
1981 out.push_str(" style=\"");
1959 out.push_str(&html_escape(&inline_css));
1982 out.push_str(&html_escape(&inline_css));
@@ -2059,7 +2082,7 @@ fn generate_self_closing(
2059 styles: &mut StyleCollector,
2082 styles: &mut StyleCollector,
2060 ctx: &mut GenContext,
2083 ctx: &mut GenContext,
2062− let gen_class = compute_class(&elem.attrs, &elem.kind, parent_kind, styles);
2085+ let gen_class = compute_class(&elem.attrs, &elem.kind, parent_kind, styles, false);
2063 let (id, user_class) = extract_id_class(&elem.attrs);
2086 let (id, user_class) = extract_id_class(&elem.attrs);
2065 let (tag, kind_label) = match elem.kind {
2088 let (tag, kind_label) = match elem.kind {
@@ -2255,13 +2278,14 @@ fn compute_class(
2255 kind: &ElementKind,
2278 kind: &ElementKind,
2256 parent_kind: Option<&ElementKind>,
2279 parent_kind: Option<&ElementKind>,
2257 styles: &mut StyleCollector,
2280 styles: &mut StyleCollector,
2281+ has_overlay_children: bool,
2258) -> Option<String> {
2282) -> Option<String> {
2259− let base = attrs_to_css(attrs, "", kind, parent_kind);
2283+ let base = attrs_to_css(attrs, "", kind, parent_kind, has_overlay_children);
2261 // Collect pseudo-state overrides
2285 // Collect pseudo-state overrides
2262 let mut pseudo = Vec::new();
2286 let mut pseudo = Vec::new();
2263 for &(prefix, selector) in PSEUDO_PREFIXES {
2287 for &(prefix, selector) in PSEUDO_PREFIXES {
2264− let css = attrs_to_css(attrs, prefix, kind, parent_kind);
2288+ let css = attrs_to_css(attrs, prefix, kind, parent_kind, has_overlay_children);
2265 if !css.is_empty() {
2289 if !css.is_empty() {
2266 pseudo.push((selector.to_string(), css));
2290 pseudo.push((selector.to_string(), css));
@@ -2291,7 +2315,7 @@ fn compute_class(
2291 for prefix in &nth_prefixes {
2315 for prefix in &nth_prefixes {
2292 let expr = &prefix[4..prefix.len() - 1];
2316 let expr = &prefix[4..prefix.len() - 1];
2293 let selector = format!(":nth-child({})", expr);
2317 let selector = format!(":nth-child({})", expr);
2294− let css = attrs_to_css(attrs, prefix, kind, parent_kind);
2318+ let css = attrs_to_css(attrs, prefix, kind, parent_kind, has_overlay_children);
2295 if !css.is_empty() {
2319 if !css.is_empty() {
2296 pseudo.push((selector, css));
2320 pseudo.push((selector, css));
@@ -2312,7 +2336,7 @@ fn compute_class(
2312 for prefix in &has_prefixes {
2336 for prefix in &has_prefixes {
2313 let inner = &prefix[4..prefix.len() - 2]; // extract selector from has(selector):
2337 let inner = &prefix[4..prefix.len() - 2]; // extract selector from has(selector):
2314 let selector = format!(":has({})", inner);
2338 let selector = format!(":has({})", inner);
2315− let css = attrs_to_css(attrs, prefix, kind, parent_kind);
2339+ let css = attrs_to_css(attrs, prefix, kind, parent_kind, has_overlay_children);
2316 if !css.is_empty() {
2340 if !css.is_empty() {
2317 pseudo.push((selector, css));
2341 pseudo.push((selector, css));
@@ -2322,7 +2346,7 @@ fn compute_class(
2322 let mut responsive = Vec::new();
2346 let mut responsive = Vec::new();
2323 for &(bp_name, _) in BREAKPOINTS {
2347 for &(bp_name, _) in BREAKPOINTS {
2324 let prefix = format!("{}:", bp_name);
2348 let prefix = format!("{}:", bp_name);
2325− let css = attrs_to_css(attrs, &prefix, kind, parent_kind);
2349+ let css = attrs_to_css(attrs, &prefix, kind, parent_kind, has_overlay_children);
2326 if !css.is_empty() {
2350 if !css.is_empty() {
2327 responsive.push((bp_name.to_string(), css));
2351 responsive.push((bp_name.to_string(), css));
@@ -2332,18 +2356,19 @@ fn compute_class(
2332 let mut container = Vec::new();
2356 let mut container = Vec::new();
2333 for &(bp_name, _) in BREAKPOINTS {
2357 for &(bp_name, _) in BREAKPOINTS {
2334 let prefix = format!("cq-{}:", bp_name);
2358 let prefix = format!("cq-{}:", bp_name);
2335− let css = attrs_to_css(attrs, &prefix, kind, parent_kind);
2359+ let css = attrs_to_css(attrs, &prefix, kind, parent_kind, has_overlay_children);
2336 if !css.is_empty() {
2360 if !css.is_empty() {
2337 container.push((bp_name.to_string(), css));
2361 container.push((bp_name.to_string(), css));
2341− let dark = attrs_to_css(attrs, "dark:", kind, parent_kind);
2365+ let dark = attrs_to_css(attrs, "dark:", kind, parent_kind, has_overlay_children);
2342− let print = attrs_to_css(attrs, "print:", kind, parent_kind);
2366+ let print = attrs_to_css(attrs, "print:", kind, parent_kind, has_overlay_children);
2343− let motion_safe = attrs_to_css(attrs, "motion-safe:", kind, parent_kind);
2367+ let motion_safe = attrs_to_css(attrs, "motion-safe:", kind, parent_kind, has_overlay_children);
2368+ let motion_reduce =
2344− let motion_reduce = attrs_to_css(attrs, "motion-reduce:", kind, parent_kind);
2369+ attrs_to_css(attrs, "motion-reduce:", kind, parent_kind, has_overlay_children);
2345− let landscape = attrs_to_css(attrs, "landscape:", kind, parent_kind);
2370+ let landscape = attrs_to_css(attrs, "landscape:", kind, parent_kind, has_overlay_children);
2346− let portrait = attrs_to_css(attrs, "portrait:", kind, parent_kind);
2371+ let portrait = attrs_to_css(attrs, "portrait:", kind, parent_kind, has_overlay_children);
2348 // Dedupe: if a property is declared twice within a single rule, keep only
2373 // Dedupe: if a property is declared twice within a single rule, keep only
2349 // the last occurrence (element-kind defaults are written before
2374 // the last occurrence (element-kind defaults are written before
@@ -2545,11 +2570,18 @@ fn attrs_to_css(
2545 state_prefix: &str,
2570 state_prefix: &str,
2546 kind: &ElementKind,
2571 kind: &ElementKind,
2547 parent_kind: Option<&ElementKind>,
2572 parent_kind: Option<&ElementKind>,
2573+ has_overlay_children: bool,
2548) -> String {
2574) -> String {
2549 let mut css = String::new();
2575 let mut css = String::new();
2551 // Base element styles only for the default (non-state) pass
2577 // Base element styles only for the default (non-state) pass
2552 if state_prefix.is_empty() {
2578 if state_prefix.is_empty() {
2579+ // Elements with @in-front / @behind children become positioning
2580+ // contexts. Pushed before user attrs so an explicit `position` wins
2581+ // via dedupe (only the last declaration of a property is kept).
2582+ if has_overlay_children {
2583+ css.push_str("position:relative;isolation:isolate;");
2553 match kind {
2585 match kind {
2554 ElementKind::Row => css.push_str("display:flex;flex-direction:row;"),
2586 ElementKind::Row => css.push_str("display:flex;flex-direction:row;"),
2555 ElementKind::Column
2587 ElementKind::Column
@@ -2613,6 +2645,16 @@ fn attrs_to_css(
2613 ElementKind::Avatar => css.push_str("display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;overflow:hidden;flex-shrink:0;"),
2645 ElementKind::Avatar => css.push_str("display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;overflow:hidden;flex-shrink:0;"),
2614 ElementKind::Carousel => css.push_str("display:flex;flex-direction:row;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;scrollbar-width:none;"),
2646 ElementKind::Carousel => css.push_str("display:flex;flex-direction:row;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;scrollbar-width:none;"),
2615 ElementKind::Chip => css.push_str("display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border-radius:9999px;font-size:0.875rem;border:1px solid currentColor;"),
2647 ElementKind::Chip => css.push_str("display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border-radius:9999px;font-size:0.875rem;border:1px solid currentColor;"),
2648+ // Overlay layers fill the parent. Parent gets position:relative
2649+ // applied automatically (see has_overlay_children check below).
2650+ ElementKind::InFront => {
2651+ css.push_str(FLEX_COLUMN);
2652+ css.push_str("position:absolute;inset:0;");
2654+ ElementKind::Behind => {
2655+ css.push_str(FLEX_COLUMN);
2656+ css.push_str("position:absolute;inset:0;z-index:-1;");
2618 // Children of @carousel get scroll-snap-align and flex-shrink
2660 // Children of @carousel get scroll-snap-align and flex-shrink