diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs
index 65e2372b6..1656e547f 100644
--- a/frontend/src/app/render_wasm/api.cljs
+++ b/frontend/src/app/render_wasm/api.cljs
@@ -108,6 +108,15 @@
             (aget buffer 2)
             (aget buffer 3))))
 
+(defn set-parent-id
+  [id]
+  (let [buffer (uuid/get-u32 id)]
+    (h/call internal-module "_set_parent"
+            (aget buffer 0)
+            (aget buffer 1)
+            (aget buffer 2)
+            (aget buffer 3))))
+
 (defn set-shape-clip-content
   [clip-content]
   (h/call internal-module "_set_shape_clip_content" clip-content))
@@ -614,6 +623,15 @@
     :fix  1
     :auto 2))
 
+(defn translate-align-self
+  [value]
+  (when value
+    (case value
+      :start   0
+      :end     1
+      :center  2
+      :stretch 3)))
+
 (defn set-layout-child
   [shape]
   (let [margins (dm/get-prop shape :layout-item-margin)
@@ -624,6 +642,7 @@
 
         h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :auto) translate-layout-sizing)
         v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :auto) translate-layout-sizing)
+        align-self (-> (dm/get-prop shape :layout-item-align-self) translate-align-self)
 
         max-h (dm/get-prop shape :layout-item-max-h)
         has-max-h (some? max-h)
@@ -651,6 +670,8 @@
             (or max-w 0)
             has-min-w
             (or min-w 0)
+            (some? align-self)
+            (or align-self 0)
             is-absolute
             z-index)))
 
@@ -794,6 +815,7 @@
           (if (< index total-shapes)
             (let [shape        (nth shapes index)
                   id           (dm/get-prop shape :id)
+                  parent-id    (dm/get-prop shape :parent-id)
                   type         (dm/get-prop shape :type)
                   masked       (dm/get-prop shape :masked-group)
                   selrect      (dm/get-prop shape :selrect)
@@ -824,6 +846,7 @@
                   shadows      (dm/get-prop shape :shadow)]
 
               (use-shape id)
+              (set-parent-id parent-id)
               (set-shape-type type)
               (set-shape-clip-content clip-content)
               (set-shape-selrect selrect)
@@ -850,7 +873,8 @@
               (when (and (= type :text) (some? content))
                 (set-shape-text-content content))
 
-              (when (ctl/any-layout-immediate-child? objects shape)
+              (when (or (ctl/any-layout? shape)
+                        (ctl/any-layout-immediate-child? objects shape))
                 (set-layout-child shape))
 
               (when (ctl/flex-layout? shape)
diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs
index dd16951d2..d38bdd1aa 100644
--- a/frontend/src/app/render_wasm/shape.cljs
+++ b/frontend/src/app/render_wasm/shape.cljs
@@ -111,6 +111,7 @@
   (when ^boolean shape/*wasm-sync*
     (api/use-shape (:id self))
     (case k
+      :parent-id    (api/set-parent-id v)
       :type         (api/set-shape-type v)
       :bool-type    (api/set-shape-bool-type v)
       :bool-content (api/set-shape-bool-content v)
diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md
index b4ea254d1..35fdab183 100644
--- a/render-wasm/docs/serialization.md
+++ b/render-wasm/docs/serialization.md
@@ -162,6 +162,16 @@ Shadow styles are serialized as `u8`:
 | 3     | Stretch |
 | \_    | error   |
 
+### Align self
+
+| Value | Field   |
+| ----- | ------- |
+| 0     | Start   |
+| 1     | End     |
+| 2     | Center  |
+| 3     | Stretch |
+| \_    | error   |
+
 ### Align Content
 
 | Value | Field         |
diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs
index b49ff216a..9b53011fb 100644
--- a/render-wasm/src/main.rs
+++ b/render-wasm/src/main.rs
@@ -167,6 +167,15 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
     });
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) {
+    let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
+    let id = uuid_from_u32_quartet(a, b, c, d);
+    if let Some(shape) = state.current_shape() {
+        shape.set_parent(id);
+    }
+}
+
 #[no_mangle]
 pub extern "C" fn set_shape_masked_group(masked: bool) {
     with_current_shape!(state, |shape: &mut Shape| {
@@ -697,6 +706,8 @@ pub extern "C" fn set_layout_child_data(
     max_w: f32,
     has_min_w: bool,
     min_w: f32,
+    has_align_self: bool,
+    align_self: u8,
     is_absolute: bool,
     z_index: i32,
 ) {
@@ -706,6 +717,11 @@ pub extern "C" fn set_layout_child_data(
     let min_h = if has_min_h { Some(min_h) } else { None };
     let max_w = if has_max_w { Some(max_w) } else { None };
     let min_w = if has_min_w { Some(min_w) } else { None };
+    let align_self = if has_align_self {
+        shapes::AlignSelf::from_u8(align_self)
+    } else {
+        None
+    };
 
     with_current_shape!(state, |shape: &mut Shape| {
         shape.set_flex_layout_child_data(
@@ -719,6 +735,7 @@ pub extern "C" fn set_layout_child_data(
             min_h,
             max_w,
             min_w,
+            align_self,
             is_absolute,
             z_index,
         );
diff --git a/render-wasm/src/math.rs b/render-wasm/src/math.rs
index 4b5f30ca8..ada204936 100644
--- a/render-wasm/src/math.rs
+++ b/render-wasm/src/math.rs
@@ -24,7 +24,7 @@ pub struct Bounds {
     pub sw: Point,
 }
 
-fn vec_min_max(arr: &[Option<f32>]) -> Option<(f32, f32)> {
+fn vec_min_max(arr: &Vec<Option<f32>>) -> Option<(f32, f32)> {
     let mut minv: Option<f32> = None;
     let mut maxv: Option<f32> = None;
 
@@ -95,26 +95,30 @@ impl Bounds {
         self.sw = mtx.map_point(self.sw);
     }
 
-    pub fn box_bounds(&self, other: &Self) -> Option<Self> {
+    // pub fn box_bounds(&self, other: &Self) -> Option<Self> {
+    //     self.from_points(other.points())
+    // }
+
+    pub fn from_points(&self, points: Vec<Point>) -> Option<Self> {
         let hv = self.horizontal_vec();
         let vv = self.vertical_vec();
 
         let hr = Ray::new(self.nw, hv);
         let vr = Ray::new(self.nw, vv);
 
-        let (min_ht, max_ht) = vec_min_max(&[
-            intersect_rays_t(&hr, &Ray::new(other.nw, vv)),
-            intersect_rays_t(&hr, &Ray::new(other.ne, vv)),
-            intersect_rays_t(&hr, &Ray::new(other.sw, vv)),
-            intersect_rays_t(&hr, &Ray::new(other.se, vv)),
-        ])?;
+        let (min_ht, max_ht) = vec_min_max(
+            &points
+                .iter()
+                .map(|p| intersect_rays_t(&hr, &Ray::new(*p, vv)))
+                .collect(),
+        )?;
 
-        let (min_vt, max_vt) = vec_min_max(&[
-            intersect_rays_t(&vr, &Ray::new(other.nw, hv)),
-            intersect_rays_t(&vr, &Ray::new(other.ne, hv)),
-            intersect_rays_t(&vr, &Ray::new(other.sw, hv)),
-            intersect_rays_t(&vr, &Ray::new(other.se, hv)),
-        ])?;
+        let (min_vt, max_vt) = vec_min_max(
+            &points
+                .iter()
+                .map(|p| intersect_rays_t(&vr, &Ray::new(*p, hv)))
+                .collect(),
+        )?;
 
         let nw = intersect_rays(&Ray::new(hr.t(min_ht), vv), &Ray::new(vr.t(min_vt), hv))?;
         let ne = intersect_rays(&Ray::new(hr.t(max_ht), vv), &Ray::new(vr.t(min_vt), hv))?;
@@ -124,6 +128,10 @@ impl Bounds {
         Some(Self { nw, ne, se, sw })
     }
 
+    pub fn points(&self) -> Vec<Point> {
+        vec![self.nw, self.ne, self.se, self.sw]
+    }
+
     pub fn left(&self, p: Point) -> f32 {
         let hr = Ray::new(p, self.horizontal_vec());
         let vr = Ray::new(self.nw, self.vertical_vec());
@@ -224,6 +232,11 @@ impl Bounds {
         result
     }
 
+    pub fn center(&self) -> Point {
+        // Calculates the centroid of the four points
+        Point::new(self.nw.x + self.se.x / 2.0, self.nw.y + self.se.y / 2.0)
+    }
+
     pub fn transform_matrix(&self) -> Option<Matrix> {
         let w2 = self.width() / 2.0;
         let h2 = self.height() / 2.0;
@@ -320,6 +333,40 @@ pub fn intersect_rays(ray1: &Ray, ray2: &Ray) -> Option<Point> {
     }
 }
 
+/*
+ * Creates a resizing matrix with width/height relative to the parent
+ * box and keepin the same transform as the parent.
+ */
+pub fn resize_matrix(
+    parent_bounds: &Bounds,
+    child_bounds: &Bounds,
+    new_width: f32,
+    new_height: f32,
+) -> Matrix {
+    let mut result = Matrix::default();
+    let scale_width = new_width / child_bounds.width();
+    let scale_height = new_height / child_bounds.height();
+
+    let center = child_bounds.center();
+    let mut parent_transform = parent_bounds
+        .transform_matrix()
+        .unwrap_or(Matrix::default());
+
+    parent_transform.post_translate(center);
+    parent_transform.pre_translate(-center);
+
+    let parent_transform_inv = &parent_transform.invert().unwrap();
+    let origin = parent_transform_inv.map_point(child_bounds.nw);
+
+    let mut scale = Matrix::scale((scale_width, scale_height));
+    scale.post_translate(origin);
+    scale.post_concat(&parent_transform);
+    scale.pre_translate(-origin);
+    scale.pre_concat(&parent_transform_inv);
+    result.post_concat(&scale);
+    result
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -364,12 +411,19 @@ mod tests {
 
     #[test]
     fn test_vec_min_max() {
-        assert_eq!(None, vec_min_max(&[]));
-        assert_eq!(None, vec_min_max(&[None, None]));
-        assert_eq!(Some((1.0, 1.0)), vec_min_max(&[None, Some(1.0)]));
+        assert_eq!(None, vec_min_max(&vec![]));
+        assert_eq!(None, vec_min_max(&vec![None, None]));
+        assert_eq!(Some((1.0, 1.0)), vec_min_max(&vec![None, Some(1.0)]));
         assert_eq!(
             Some((0.0, 1.0)),
-            vec_min_max(&[Some(0.3), None, Some(0.0), Some(0.7), Some(1.0), Some(0.1)])
+            vec_min_max(&vec![
+                Some(0.3),
+                None,
+                Some(0.0),
+                Some(0.7),
+                Some(1.0),
+                Some(0.1)
+            ])
         );
     }
 
diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs
index fd21ccc49..cc7494500 100644
--- a/render-wasm/src/shapes.rs
+++ b/render-wasm/src/shapes.rs
@@ -153,9 +153,9 @@ impl ConstraintV {
 pub type Color = skia::Color;
 
 #[derive(Debug, Clone)]
-#[allow(dead_code)]
 pub struct Shape {
     pub id: Uuid,
+    pub parent_id: Option<Uuid>,
     pub shape_type: Type,
     pub children: Vec<Uuid>,
     pub selrect: math::Rect,
@@ -180,6 +180,7 @@ impl Shape {
     pub fn new(id: Uuid) -> Self {
         Self {
             id,
+            parent_id: None,
             shape_type: Type::Rect(Rect::default()),
             children: Vec::<Uuid>::new(),
             selrect: math::Rect::new_empty(),
@@ -201,14 +202,32 @@ impl Shape {
         }
     }
 
+    pub fn set_parent(&mut self, id: Uuid) {
+        self.parent_id = Some(id);
+    }
+
     pub fn set_shape_type(&mut self, shape_type: Type) {
         self.shape_type = shape_type;
     }
 
+    #[allow(dead_code)]
     pub fn is_frame(&self) -> bool {
         matches!(self.shape_type, Type::Frame(_))
     }
 
+    pub fn is_group_like(&self) -> bool {
+        matches!(self.shape_type, Type::Group(_)) || matches!(self.shape_type, Type::Bool(_))
+    }
+
+    pub fn has_layout(&self) -> bool {
+        match self.shape_type {
+            Type::Frame(Frame {
+                layout: Some(_), ..
+            }) => true,
+            _ => false,
+        }
+    }
+
     pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
         self.selrect.set_ltrb(left, top, right, bottom);
         match self.shape_type {
@@ -276,6 +295,7 @@ impl Shape {
         min_h: Option<f32>,
         max_w: Option<f32>,
         min_w: Option<f32>,
+        align_self: Option<AlignSelf>,
         is_absolute: bool,
         z_index: i32,
     ) {
@@ -292,6 +312,7 @@ impl Shape {
             min_w,
             is_absolute,
             z_index,
+            align_self,
         });
     }
 
@@ -654,6 +675,34 @@ impl Shape {
             _ => {}
         }
     }
+
+    pub fn is_layout_vertical_auto(&self) -> bool {
+        match &self.layout_item {
+            Some(LayoutItem { v_sizing, .. }) => v_sizing == &Sizing::Auto,
+            _ => false,
+        }
+    }
+
+    pub fn is_layout_vertical_fill(&self) -> bool {
+        match &self.layout_item {
+            Some(LayoutItem { v_sizing, .. }) => v_sizing == &Sizing::Fill,
+            _ => false,
+        }
+    }
+
+    pub fn is_layout_horizontal_auto(&self) -> bool {
+        match &self.layout_item {
+            Some(LayoutItem { h_sizing, .. }) => h_sizing == &Sizing::Auto,
+            _ => false,
+        }
+    }
+
+    pub fn is_layout_horizontal_fill(&self) -> bool {
+        match &self.layout_item {
+            Some(LayoutItem { h_sizing, .. }) => h_sizing == &Sizing::Fill,
+            _ => false,
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs
index d6741b0b8..3dcdf2e16 100644
--- a/render-wasm/src/shapes/layouts.rs
+++ b/render-wasm/src/shapes/layouts.rs
@@ -1,6 +1,5 @@
-#![allow(dead_code)]
-
 #[derive(Debug, Clone, PartialEq)]
+#[allow(dead_code)]
 pub enum Layout {
     FlexLayout(LayoutData, FlexData),
     GridLayout(LayoutData, GridData),
@@ -137,7 +136,7 @@ impl WrapType {
 #[derive(Debug, Clone, PartialEq)]
 pub struct GridTrack {}
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Copy)]
 pub enum Sizing {
     Fill,
     Fix,
@@ -168,6 +167,49 @@ pub struct LayoutData {
     pub padding_left: f32,
 }
 
+impl LayoutData {
+    pub fn is_reverse(&self) -> bool {
+        match &self.direction {
+            Direction::RowReverse | Direction::ColumnReverse => true,
+            _ => false,
+        }
+    }
+    pub fn is_row(&self) -> bool {
+        match &self.direction {
+            Direction::RowReverse | Direction::Row => true,
+            _ => false,
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn is_column(&self) -> bool {
+        match &self.direction {
+            Direction::ColumnReverse | Direction::Column => true,
+            _ => false,
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum AlignSelf {
+    Start,
+    End,
+    Center,
+    Stretch,
+}
+
+impl AlignSelf {
+    pub fn from_u8(value: u8) -> Option<AlignSelf> {
+        match value {
+            0 => Some(Self::Start),
+            1 => Some(Self::End),
+            2 => Some(Self::Center),
+            3 => Some(Self::Stretch),
+            _ => None,
+        }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq)]
 pub struct FlexData {
     pub row_gap: f32,
@@ -175,6 +217,15 @@ pub struct FlexData {
     pub wrap_type: WrapType,
 }
 
+impl FlexData {
+    pub fn is_wrap(&self) -> bool {
+        match self.wrap_type {
+            WrapType::Wrap => true,
+            _ => false,
+        }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq)]
 pub struct GridData {
     pub rows: Vec<GridTrack>,
@@ -182,7 +233,7 @@ pub struct GridData {
     // layout-grid-cells       ;; map of id->grid-cell
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Copy)]
 pub struct LayoutItem {
     pub margin_top: f32,
     pub margin_right: f32,
@@ -196,4 +247,5 @@ pub struct LayoutItem {
     pub min_w: Option<f32>,
     pub is_absolute: bool,
     pub z_index: i32,
+    pub align_self: Option<AlignSelf>,
 }
diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs
index f11ed0214..2ca02ffaa 100644
--- a/render-wasm/src/shapes/modifiers.rs
+++ b/render-wasm/src/shapes/modifiers.rs
@@ -1,220 +1,228 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet, VecDeque};
 
-use skia::Matrix;
-use skia_safe as skia;
+mod common;
+mod constraints;
+mod flex_layout;
+mod grid_layout;
 
-use std::collections::HashSet;
 use uuid::Uuid;
 
-use crate::math::Bounds;
-use crate::shapes::{ConstraintH, ConstraintV, Shape, TransformEntry};
+use common::GetBounds;
+
+use crate::math::{Bounds, Matrix, Point};
+use crate::shapes::{
+    ConstraintH, ConstraintV, Frame, Group, Layout, Modifier, Shape, TransformEntry, Type,
+};
 use crate::state::State;
 
-fn calculate_resize(
-    constraint_h: ConstraintH,
-    constraint_v: ConstraintV,
-    parent_before: &Bounds,
-    parent_after: &Bounds,
-    child_before: &Bounds,
-    child_after: &Bounds,
-) -> Option<(f32, f32)> {
-    let scale_width = match constraint_h {
-        ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => {
-            parent_before.width() / parent_after.width()
-        }
-        ConstraintH::LeftRight => {
-            let left = parent_before.left(child_before.nw);
-            let right = parent_before.right(child_before.ne);
-            let target_width = parent_after.width() - left - right;
-            target_width / child_after.width()
-        }
-        _ => 1.0,
-    };
-
-    let scale_height = match constraint_v {
-        ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => {
-            parent_before.height() / parent_after.height()
-        }
-        ConstraintV::TopBottom => {
-            let top = parent_before.top(child_before.nw);
-            let bottom = parent_before.bottom(child_before.sw);
-            let target_height = parent_after.height() - top - bottom;
-            target_height / child_after.height()
-        }
-        _ => 1.0,
-    };
-
-    if (scale_width - 1.0).abs() < f32::EPSILON && (scale_height - 1.0).abs() < f32::EPSILON {
-        None
-    } else {
-        Some((scale_width, scale_height))
-    }
-}
-
-fn calculate_displacement(
-    constraint_h: ConstraintH,
-    constraint_v: ConstraintV,
-    parent_before: &Bounds,
-    parent_after: &Bounds,
-    child_before: &Bounds,
-    child_after: &Bounds,
-) -> Option<(f32, f32)> {
-    let delta_x = match constraint_h {
-        ConstraintH::Left | ConstraintH::LeftRight => {
-            let target_left = parent_before.left(child_before.nw);
-            let current_left = parent_after.left(child_after.nw);
-            target_left - current_left
-        }
-        ConstraintH::Right => {
-            let target_right = parent_before.right(child_before.ne);
-            let current_right = parent_after.right(child_after.ne);
-            current_right - target_right
-        }
-        ConstraintH::Center => {
-            let delta_width = parent_after.width() - parent_before.width();
-            let target_left = parent_before.left(child_before.nw);
-            let current_left = parent_after.left(child_after.nw);
-            target_left - current_left + delta_width / 2.0
-        }
-        _ => 0.0,
-    };
-
-    let delta_y = match constraint_v {
-        ConstraintV::Top | ConstraintV::TopBottom => {
-            let target_top = parent_before.top(child_before.nw);
-            let current_top = parent_after.top(child_after.nw);
-            target_top - current_top
-        }
-        ConstraintV::Bottom => {
-            let target_bottom = parent_before.bottom(child_before.ne);
-            let current_bottom = parent_after.bottom(child_after.ne);
-            current_bottom - target_bottom
-        }
-        ConstraintV::Center => {
-            let delta_height = parent_after.height() - parent_before.height();
-            let target_top = parent_before.top(child_before.nw);
-            let current_top = parent_after.top(child_after.nw);
-            target_top - current_top + delta_height / 2.0
-        }
-        _ => 0.0,
-    };
-
-    if delta_x.abs() < f32::EPSILON && delta_y.abs() < f32::EPSILON {
-        None
-    } else {
-        Some((delta_x, delta_y))
-    }
-}
-
-fn propagate_shape(
-    shapes: &HashMap<Uuid, Shape>,
+fn propagate_children(
     shape: &Shape,
+    shapes: &HashMap<Uuid, Shape>,
+    parent_bounds_before: &Bounds,
+    parent_bounds_after: &Bounds,
     transform: Matrix,
-) -> Vec<TransformEntry> {
+    bounds: &HashMap<Uuid, Bounds>,
+) -> VecDeque<Modifier> {
     if shape.children.len() == 0 {
-        return vec![];
+        return VecDeque::new();
     }
 
-    let parent_bounds_before = shape.bounds();
-    let parent_bounds_after = parent_bounds_before.transform(&transform);
-    let mut result = Vec::new();
+    let mut result = VecDeque::new();
 
     for child_id in shape.children.iter() {
-        if let Some(child) = shapes.get(child_id) {
-            let constraint_h = child.constraint_h(if shape.is_frame() {
-                ConstraintH::Left
-            } else {
-                ConstraintH::Scale
-            });
+        let Some(child) = shapes.get(child_id) else {
+            continue;
+        };
 
-            let constraint_v = child.constraint_v(if shape.is_frame() {
-                ConstraintV::Top
-            } else {
-                ConstraintV::Scale
-            });
-            // if the constrains are scale & scale or the transform has only moves we
-            // can propagate as is
-            if (constraint_h == ConstraintH::Scale && constraint_v == ConstraintV::Scale)
-                || transform.is_translate()
-            {
-                result.push(TransformEntry::new(child_id.clone(), transform));
-                continue;
-            }
+        let child_bounds = bounds.find(child);
 
-            if let Some(child_bounds_before) = parent_bounds_before.box_bounds(&child.bounds()) {
-                let mut transform = transform;
-                let mut child_bounds_after = child_bounds_before.transform(&transform);
+        let constraint_h = match &shape.shape_type {
+            Type::Frame(Frame {
+                layout: Some(_), ..
+            }) => ConstraintH::Left,
+            Type::Frame(_) => child.constraint_h(ConstraintH::Left),
+            _ => child.constraint_h(ConstraintH::Scale),
+        };
 
-                // Scale shape
-                if let Some((scale_width, scale_height)) = calculate_resize(
-                    constraint_h,
-                    constraint_v,
-                    &parent_bounds_before,
-                    &parent_bounds_after,
-                    &child_bounds_before,
-                    &child_bounds_after,
-                ) {
-                    let center = child.center();
+        let constraint_v = match &shape.shape_type {
+            Type::Frame(Frame {
+                layout: Some(_), ..
+            }) => ConstraintV::Top,
+            Type::Frame(_) => child.constraint_v(ConstraintV::Top),
+            _ => child.constraint_v(ConstraintV::Scale),
+        };
 
-                    let mut parent_transform = parent_bounds_after
-                        .transform_matrix()
-                        .unwrap_or(Matrix::default());
-                    parent_transform.post_translate(center);
-                    parent_transform.pre_translate(-center);
+        let transform = constraints::propagate_shape_constraints(
+            &parent_bounds_before,
+            &parent_bounds_after,
+            &child_bounds,
+            constraint_h,
+            constraint_v,
+            transform,
+        );
 
-                    let parent_transform_inv = &parent_transform.invert().unwrap();
-                    let origin = parent_transform_inv.map_point(child_bounds_after.nw);
-
-                    let mut scale = Matrix::scale((scale_width, scale_height));
-                    scale.post_translate(origin);
-                    scale.post_concat(&parent_transform);
-                    scale.pre_translate(-origin);
-                    scale.pre_concat(&parent_transform_inv);
-
-                    child_bounds_after.transform_mut(&scale);
-                    transform.post_concat(&scale);
-                }
-
-                // Translate position
-                if let Some((delta_x, delta_y)) = calculate_displacement(
-                    constraint_h,
-                    constraint_v,
-                    &parent_bounds_before,
-                    &parent_bounds_after,
-                    &child_bounds_before,
-                    &child_bounds_after,
-                ) {
-                    let th = parent_bounds_after.hv(delta_x);
-                    let tv = parent_bounds_after.vv(delta_y);
-                    transform.post_concat(&Matrix::translate(th + tv));
-                }
-
-                result.push(TransformEntry::new(child_id.clone(), transform));
-            }
-        }
+        result.push_back(Modifier::transform(*child_id, transform));
     }
 
     result
 }
 
+fn calculate_group_bounds(
+    shape: &Shape,
+    shapes: &HashMap<Uuid, Shape>,
+    bounds: &HashMap<Uuid, Bounds>,
+) -> Option<Bounds> {
+    let shape_bounds = bounds.find(&shape);
+    let mut result = Vec::<Point>::new();
+    for child_id in shape.children.iter() {
+        let Some(child) = shapes.get(child_id) else {
+            continue;
+        };
+
+        let child_bounds = bounds.find(child);
+        result.append(&mut child_bounds.points());
+    }
+
+    shape_bounds.from_points(result)
+}
+
 pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec<TransformEntry> {
-    let mut entries = modifiers.clone();
-    let mut processed = HashSet::<Uuid>::new();
-    let mut result = Vec::<TransformEntry>::new();
+    let shapes = &state.shapes;
 
-    // Propagate the transform to children
-    while let Some(entry) = entries.pop() {
-        if !processed.contains(&entry.id) {
-            if let Some(shape) = state.shapes.get(&entry.id) {
-                let mut children = propagate_shape(&state.shapes, shape, entry.transform);
-                entries.append(&mut children);
-                processed.insert(entry.id);
-                result.push(entry.clone());
+    let mut entries: VecDeque<_> = modifiers
+        .iter()
+        .map(|entry| Modifier::Transform(entry.clone()))
+        .collect();
+    let mut modifiers = HashMap::<Uuid, Matrix>::new();
+    let mut bounds = HashMap::<Uuid, Bounds>::new();
+
+    let mut reflown = HashSet::<Uuid>::new();
+    let mut layout_reflows = Vec::<Uuid>::new();
+
+    // We first propagate the transforms to the children and then after
+    // recalculate the layouts. The layout can create further transforms that
+    // we need to re-propagate.
+    // In order for loop to eventualy finish, we limit the flex reflow to just
+    // one (the reflown set).
+    while !entries.is_empty() {
+        while let Some(modifier) = entries.pop_front() {
+            match modifier {
+                Modifier::Transform(entry) => {
+                    // println!("Transform {}", entry.id);
+                    let Some(shape) = state.shapes.get(&entry.id) else {
+                        continue;
+                    };
+
+                    let shape_bounds_before = bounds.find(&shape);
+                    let shape_bounds_after = shape_bounds_before.transform(&entry.transform);
+
+                    if entry.propagate {
+                        let mut children = propagate_children(
+                            shape,
+                            shapes,
+                            &shape_bounds_before,
+                            &shape_bounds_after,
+                            entry.transform,
+                            &bounds,
+                        );
+
+                        entries.append(&mut children);
+                    }
+
+                    bounds.insert(shape.id, shape_bounds_after);
+
+                    let default_matrix = Matrix::default();
+                    let mut shape_modif =
+                        modifiers.get(&shape.id).unwrap_or(&default_matrix).clone();
+                    shape_modif.post_concat(&entry.transform);
+                    modifiers.insert(shape.id, shape_modif);
+
+                    if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
+                        if parent.has_layout() || parent.is_group_like() {
+                            entries.push_back(Modifier::reflow(parent.id));
+                        }
+                    }
+                }
+
+                Modifier::Reflow(id) => {
+                    let Some(shape) = state.shapes.get(&id) else {
+                        continue;
+                    };
+
+                    match &shape.shape_type {
+                        Type::Frame(Frame {
+                            layout: Some(_), ..
+                        }) => {
+                            if !reflown.contains(&id) {
+                                layout_reflows.push(id);
+                                reflown.insert(id);
+                            }
+                        }
+                        Type::Group(Group { masked: true }) => {
+                            if let Some(child) = shapes.get(&shape.children[0]) {
+                                let child_bounds = bounds.find(&child);
+                                bounds.insert(shape.id, child_bounds);
+                            }
+                        }
+                        Type::Group(_) => {
+                            if let Some(shape_bounds) =
+                                calculate_group_bounds(shape, shapes, &bounds)
+                            {
+                                bounds.insert(shape.id, shape_bounds);
+                            }
+                        }
+                        Type::Bool(_) => {
+                            // TODO: How to calculate from rust the new box? we need to calculate the
+                            // new path... impossible right now. I'm going to use for the moment the group
+                            // calculation
+                            if let Some(shape_bounds) =
+                                calculate_group_bounds(shape, shapes, &bounds)
+                            {
+                                bounds.insert(shape.id, shape_bounds);
+                            }
+                        }
+                        _ => {
+                            // Other shapes don't have to be reflown
+                        }
+                    }
+
+                    if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
+                        if parent.has_layout() || parent.is_group_like() {
+                            entries.push_back(Modifier::reflow(parent.id));
+                        }
+                    }
+                }
             }
         }
+
+        for id in layout_reflows.iter() {
+            let Some(shape) = state.shapes.get(&id) else {
+                continue;
+            };
+
+            let Type::Frame(frame_data) = &shape.shape_type else {
+                continue;
+            };
+
+            if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
+                let mut children =
+                    flex_layout::reflow_flex_layout(shape, layout_data, flex_data, shapes, &bounds);
+                entries.append(&mut children);
+            }
+
+            if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
+                let mut children =
+                    grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, &bounds);
+                entries.append(&mut children);
+            }
+        }
+        layout_reflows = Vec::new();
     }
 
-    result
+    modifiers
+        .iter()
+        .map(|(key, val)| TransformEntry::new(*key, *val))
+        .collect()
 }
 
 #[cfg(test)]
@@ -246,8 +254,47 @@ mod tests {
         transform.post_translate(Point::new(x, y));
         transform.pre_translate(Point::new(-x, -y));
 
-        let result = propagate_shape(&shapes, &parent, transform);
+        let bounds_before = parent.bounds();
+        let bounds_after = bounds_before.transform(&transform);
+
+        let result = propagate_children(
+            &parent,
+            &shapes,
+            &bounds_before,
+            &bounds_after,
+            transform,
+            &HashMap::<Uuid, Bounds>::new(),
+        );
 
         assert_eq!(result.len(), 1);
     }
+
+    #[test]
+    fn test_group_bounds() {
+        let mut shapes = HashMap::<Uuid, Shape>::new();
+
+        let child1_id = Uuid::new_v4();
+        let mut child1 = Shape::new(child1_id);
+        child1.set_selrect(3.0, 3.0, 2.0, 2.0);
+        shapes.insert(child1_id, child1);
+
+        let child2_id = Uuid::new_v4();
+        let mut child2 = Shape::new(child2_id);
+        child2.set_selrect(0.0, 0.0, 1.0, 1.0);
+        shapes.insert(child2_id, child2);
+
+        let parent_id = Uuid::new_v4();
+        let mut parent = Shape::new(parent_id);
+        parent.set_shape_type(Type::Group(Group::default()));
+        parent.add_child(child1_id);
+        parent.add_child(child2_id);
+        parent.set_selrect(0.0, 0.0, 3.0, 3.0);
+        shapes.insert(parent_id, parent.clone());
+
+        let bounds =
+            calculate_group_bounds(&parent, &shapes, HashMap::<Uuid, Bounds>::new()).unwrap();
+
+        assert_eq!(bounds.width(), 3.0);
+        assert_eq!(bounds.height(), 3.0);
+    }
 }
diff --git a/render-wasm/src/shapes/modifiers/common.rs b/render-wasm/src/shapes/modifiers/common.rs
new file mode 100644
index 000000000..9324adb90
--- /dev/null
+++ b/render-wasm/src/shapes/modifiers/common.rs
@@ -0,0 +1,17 @@
+use std::collections::HashMap;
+use uuid::Uuid;
+
+use crate::math::Bounds;
+use crate::shapes::Shape;
+
+pub trait GetBounds {
+    fn find(&self, shape: &Shape) -> Bounds;
+}
+
+impl GetBounds for HashMap<Uuid, Bounds> {
+    fn find(&self, shape: &Shape) -> Bounds {
+        self.get(&shape.id)
+            .map(|b| b.clone())
+            .unwrap_or(shape.bounds())
+    }
+}
diff --git a/render-wasm/src/shapes/modifiers/constraints.rs b/render-wasm/src/shapes/modifiers/constraints.rs
new file mode 100644
index 000000000..ae59cf8cd
--- /dev/null
+++ b/render-wasm/src/shapes/modifiers/constraints.rs
@@ -0,0 +1,164 @@
+use crate::math::{Bounds, Matrix};
+use crate::shapes::{ConstraintH, ConstraintV};
+
+pub fn calculate_resize(
+    constraint_h: ConstraintH,
+    constraint_v: ConstraintV,
+    parent_before: &Bounds,
+    parent_after: &Bounds,
+    child_before: &Bounds,
+    child_after: &Bounds,
+) -> Option<(f32, f32)> {
+    let scale_width = match constraint_h {
+        ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => {
+            parent_before.width() / parent_after.width()
+        }
+        ConstraintH::LeftRight => {
+            let left = parent_before.left(child_before.nw);
+            let right = parent_before.right(child_before.ne);
+            let target_width = parent_after.width() - left - right;
+            target_width / child_after.width()
+        }
+        _ => 1.0,
+    };
+
+    let scale_height = match constraint_v {
+        ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => {
+            parent_before.height() / parent_after.height()
+        }
+        ConstraintV::TopBottom => {
+            let top = parent_before.top(child_before.nw);
+            let bottom = parent_before.bottom(child_before.sw);
+            let target_height = parent_after.height() - top - bottom;
+            target_height / child_after.height()
+        }
+        _ => 1.0,
+    };
+
+    if (scale_width - 1.0).abs() < f32::EPSILON && (scale_height - 1.0).abs() < f32::EPSILON {
+        None
+    } else {
+        Some((scale_width, scale_height))
+    }
+}
+
+pub fn calculate_displacement(
+    constraint_h: ConstraintH,
+    constraint_v: ConstraintV,
+    parent_before: &Bounds,
+    parent_after: &Bounds,
+    child_before: &Bounds,
+    child_after: &Bounds,
+) -> Option<(f32, f32)> {
+    let delta_x = match constraint_h {
+        ConstraintH::Left | ConstraintH::LeftRight => {
+            let target_left = parent_before.left(child_before.nw);
+            let current_left = parent_after.left(child_after.nw);
+            target_left - current_left
+        }
+        ConstraintH::Right => {
+            let target_right = parent_before.right(child_before.ne);
+            let current_right = parent_after.right(child_after.ne);
+            current_right - target_right
+        }
+        ConstraintH::Center => {
+            let delta_width = parent_after.width() - parent_before.width();
+            let target_left = parent_before.left(child_before.nw);
+            let current_left = parent_after.left(child_after.nw);
+            target_left - current_left + delta_width / 2.0
+        }
+        _ => 0.0,
+    };
+
+    let delta_y = match constraint_v {
+        ConstraintV::Top | ConstraintV::TopBottom => {
+            let target_top = parent_before.top(child_before.nw);
+            let current_top = parent_after.top(child_after.nw);
+            target_top - current_top
+        }
+        ConstraintV::Bottom => {
+            let target_bottom = parent_before.bottom(child_before.ne);
+            let current_bottom = parent_after.bottom(child_after.ne);
+            current_bottom - target_bottom
+        }
+        ConstraintV::Center => {
+            let delta_height = parent_after.height() - parent_before.height();
+            let target_top = parent_before.top(child_before.nw);
+            let current_top = parent_after.top(child_after.nw);
+            target_top - current_top + delta_height / 2.0
+        }
+        _ => 0.0,
+    };
+
+    if delta_x.abs() < f32::EPSILON && delta_y.abs() < f32::EPSILON {
+        None
+    } else {
+        Some((delta_x, delta_y))
+    }
+}
+
+pub fn propagate_shape_constraints(
+    parent_bounds_before: &Bounds,
+    parent_bounds_after: &Bounds,
+    child_bounds_before: &Bounds,
+    constraint_h: ConstraintH,
+    constraint_v: ConstraintV,
+    transform: Matrix,
+) -> Matrix {
+    // if the constrains are scale & scale or the transform has only moves we
+    // can propagate as is
+    if (constraint_h == ConstraintH::Scale && constraint_v == ConstraintV::Scale)
+        || transform.is_translate()
+    {
+        return transform;
+    }
+
+    let mut transform = transform;
+    let mut child_bounds_after = child_bounds_before.transform(&transform);
+
+    // Scale shape
+    if let Some((scale_width, scale_height)) = calculate_resize(
+        constraint_h,
+        constraint_v,
+        &parent_bounds_before,
+        &parent_bounds_after,
+        &child_bounds_before,
+        &child_bounds_after,
+    ) {
+        let center = child_bounds_before.center();
+
+        let mut parent_transform = parent_bounds_after
+            .transform_matrix()
+            .unwrap_or(Matrix::default());
+        parent_transform.post_translate(center);
+        parent_transform.pre_translate(-center);
+
+        let parent_transform_inv = &parent_transform.invert().unwrap();
+        let origin = parent_transform_inv.map_point(child_bounds_after.nw);
+
+        let mut scale = Matrix::scale((scale_width, scale_height));
+        scale.post_translate(origin);
+        scale.post_concat(&parent_transform);
+        scale.pre_translate(-origin);
+        scale.pre_concat(&parent_transform_inv);
+
+        child_bounds_after.transform_mut(&scale);
+        transform.post_concat(&scale);
+    }
+
+    // Translate position
+    if let Some((delta_x, delta_y)) = calculate_displacement(
+        constraint_h,
+        constraint_v,
+        &parent_bounds_before,
+        &parent_bounds_after,
+        &child_bounds_before,
+        &child_bounds_after,
+    ) {
+        let th = parent_bounds_after.hv(delta_x);
+        let tv = parent_bounds_after.vv(delta_y);
+        transform.post_concat(&Matrix::translate(th + tv));
+    }
+
+    transform
+}
diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs
new file mode 100644
index 000000000..10ec4caff
--- /dev/null
+++ b/render-wasm/src/shapes/modifiers/flex_layout.rs
@@ -0,0 +1,701 @@
+#![allow(dead_code)]
+use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
+use crate::shapes::{
+    AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
+    Modifier, Shape,
+};
+use std::collections::{HashMap, VecDeque};
+use uuid::Uuid;
+
+use super::common::GetBounds;
+
+const MIN_SIZE: f32 = 0.01;
+const MAX_SIZE: f32 = f32::INFINITY;
+
+#[derive(Debug)]
+struct TrackData {
+    main_size: f32,
+    across_size: f32,
+    max_across_size: f32,
+    is_fill_across: bool,
+    shapes: Vec<ChildAxis>,
+    anchor: Point,
+}
+
+impl TrackData {
+    fn default() -> Self {
+        Self {
+            main_size: MIN_SIZE,
+            across_size: MIN_SIZE,
+            max_across_size: MAX_SIZE,
+            is_fill_across: false,
+            shapes: Vec::new(),
+            anchor: Point::default(),
+        }
+    }
+}
+
+#[derive(Debug)]
+struct LayoutAxis {
+    main_size: f32,
+    across_size: f32,
+    main_v: Vector,
+    across_v: Vector,
+    padding_main_start: f32,
+    padding_main_end: f32,
+    padding_across_start: f32,
+    padding_across_end: f32,
+    gap_main: f32,
+    gap_across: f32,
+    is_auto_main: bool,
+    is_auto_across: bool,
+}
+
+impl LayoutAxis {
+    fn main_space(&self) -> f32 {
+        self.main_size - self.padding_main_start - self.padding_main_end
+    }
+    fn across_space(&self) -> f32 {
+        self.across_size - self.padding_across_start - self.padding_across_end
+    }
+}
+
+impl LayoutAxis {
+    fn new(
+        shape: &Shape,
+        layout_bounds: &Bounds,
+        layout_data: &LayoutData,
+        flex_data: &FlexData,
+    ) -> Self {
+        if layout_data.is_row() {
+            Self {
+                main_size: layout_bounds.width(),
+                across_size: layout_bounds.height(),
+                main_v: layout_bounds.hv(1.0),
+                across_v: layout_bounds.vv(1.0),
+                padding_main_start: layout_data.padding_left,
+                padding_main_end: layout_data.padding_right,
+                padding_across_start: layout_data.padding_top,
+                padding_across_end: layout_data.padding_bottom,
+                gap_main: flex_data.column_gap,
+                gap_across: flex_data.row_gap,
+                is_auto_main: shape.is_layout_horizontal_auto(),
+                is_auto_across: shape.is_layout_vertical_auto(),
+            }
+        } else {
+            Self {
+                main_size: layout_bounds.height(),
+                across_size: layout_bounds.width(),
+                main_v: layout_bounds.vv(1.0),
+                across_v: layout_bounds.hv(1.0),
+                padding_main_start: layout_data.padding_top,
+                padding_main_end: layout_data.padding_bottom,
+                padding_across_start: layout_data.padding_left,
+                padding_across_end: layout_data.padding_right,
+                gap_main: flex_data.row_gap,
+                gap_across: flex_data.column_gap,
+                is_auto_main: shape.is_layout_vertical_auto(),
+                is_auto_across: shape.is_layout_horizontal_auto(),
+            }
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+struct ChildAxis {
+    id: Uuid,
+    main_size: f32,
+    across_size: f32,
+    margin_main_start: f32,
+    margin_main_end: f32,
+    margin_across_start: f32,
+    margin_across_end: f32,
+    min_main_size: f32,
+    max_main_size: f32,
+    min_across_size: f32,
+    max_across_size: f32,
+    is_fill_main: bool,
+    is_fill_across: bool,
+    is_absolute: bool,
+    z_index: i32,
+}
+
+impl ChildAxis {
+    fn new(child: &Shape, child_bounds: &Bounds, layout_data: &LayoutData) -> Self {
+        let id = child.id;
+        let layout_item = child.layout_item;
+        let mut result = if layout_data.is_row() {
+            Self {
+                id,
+                main_size: child_bounds.width(),
+                across_size: child_bounds.height(),
+                margin_main_start: layout_item.map(|i| i.margin_left).unwrap_or(0.0),
+                margin_main_end: layout_item.map(|i| i.margin_right).unwrap_or(0.0),
+                margin_across_start: layout_item.map(|i| i.margin_top).unwrap_or(0.0),
+                margin_across_end: layout_item.map(|i| i.margin_bottom).unwrap_or(0.0),
+                min_main_size: layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE),
+                max_main_size: layout_item.and_then(|i| i.max_w).unwrap_or(MAX_SIZE),
+                min_across_size: layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE),
+                max_across_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
+                is_fill_main: child.is_layout_horizontal_fill(),
+                is_fill_across: child.is_layout_vertical_fill(),
+                is_absolute: layout_item.map(|i| i.is_absolute).unwrap_or(false),
+                z_index: layout_item.map(|i| i.z_index).unwrap_or(0),
+            }
+        } else {
+            Self {
+                id,
+                across_size: child_bounds.width(),
+                main_size: child_bounds.height(),
+                margin_across_start: layout_item.map(|i| i.margin_left).unwrap_or(0.0),
+                margin_across_end: layout_item.map(|i| i.margin_right).unwrap_or(0.0),
+                margin_main_start: layout_item.map(|i| i.margin_top).unwrap_or(0.0),
+                margin_main_end: layout_item.map(|i| i.margin_bottom).unwrap_or(0.0),
+                min_across_size: layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE),
+                max_across_size: layout_item.and_then(|i| i.max_w).unwrap_or(MAX_SIZE),
+                min_main_size: layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE),
+                max_main_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
+                is_fill_main: child.is_layout_vertical_fill(),
+                is_fill_across: child.is_layout_horizontal_fill(),
+                is_absolute: layout_item.map(|i| i.is_absolute).unwrap_or(false),
+                z_index: layout_item.map(|i| i.z_index).unwrap_or(0),
+            }
+        };
+
+        if result.is_fill_main {
+            result.main_size = result.min_main_size;
+        }
+        if result.is_fill_across {
+            result.across_size = result.min_across_size;
+        }
+        result
+    }
+}
+
+fn initialize_tracks(
+    shape: &Shape,
+    layout_axis: &LayoutAxis,
+    layout_data: &LayoutData,
+    flex_data: &FlexData,
+    shapes: &HashMap<Uuid, Shape>,
+    bounds: &HashMap<Uuid, Bounds>,
+) -> Vec<TrackData> {
+    let mut tracks = Vec::<TrackData>::new();
+    let mut current_track = TrackData::default();
+    let mut children = shape.children.clone();
+    let mut first = true;
+
+    if !layout_data.is_reverse() {
+        children.reverse();
+    }
+
+    for child_id in children.iter() {
+        let Some(child) = shapes.get(child_id) else {
+            continue;
+        };
+
+        let child_bounds = bounds.find(child);
+        let child_axis = ChildAxis::new(child, &child_bounds, layout_data);
+
+        let child_main_size = if child_axis.is_fill_main {
+            child_axis.min_main_size
+        } else {
+            child_axis.main_size
+        };
+        let child_across_size = if child_axis.is_fill_across {
+            child_axis.min_across_size
+        } else {
+            child_axis.across_size
+        };
+
+        let child_max_across_size = if child_axis.is_fill_across {
+            child_axis.max_across_size
+        } else {
+            child_axis.across_size
+        };
+
+        let gap_main = if first { 0.0 } else { layout_axis.gap_main };
+        let next_main_size = current_track.main_size + child_main_size + gap_main;
+
+        if !layout_axis.is_auto_main
+            && flex_data.is_wrap()
+            && (next_main_size > layout_axis.main_space())
+        {
+            tracks.push(current_track);
+
+            current_track = TrackData {
+                main_size: child_main_size,
+                across_size: child_across_size,
+                shapes: Vec::from([child_axis]),
+                is_fill_across: child_axis.is_fill_across,
+                anchor: Point::default(),
+                max_across_size: child_max_across_size,
+            };
+        } else {
+            // Update current track
+            current_track.main_size = next_main_size;
+            current_track.across_size = f32::max(child_across_size, current_track.across_size);
+            current_track.shapes.push(child_axis);
+            current_track.is_fill_across =
+                current_track.is_fill_across || child_axis.is_fill_across;
+            current_track.max_across_size =
+                f32::max(current_track.max_across_size, child_max_across_size);
+        }
+
+        first = false;
+    }
+
+    // Finalize current track
+    tracks.push(current_track);
+
+    tracks
+}
+
+// Resize main axis fill
+fn distribute_fill_main_space(layout_axis: &LayoutAxis, tracks: &mut Vec<TrackData>) {
+    for track in tracks.iter_mut() {
+        let mut left_space = layout_axis.main_space() - track.main_size;
+        let mut to_resize_children: Vec<&mut ChildAxis> = Vec::new();
+
+        for child in track.shapes.iter_mut() {
+            if child.is_fill_main && child.main_size < child.max_main_size {
+                to_resize_children.push(child);
+            }
+        }
+
+        while left_space > MIN_SIZE && !to_resize_children.is_empty() {
+            let current = left_space / to_resize_children.len() as f32;
+            for i in (0..to_resize_children.len()).rev() {
+                let child = &mut to_resize_children[i];
+                let delta =
+                    f32::min(child.max_main_size, child.main_size + current) - child.main_size;
+                child.main_size = child.main_size + delta;
+                left_space = left_space - delta;
+
+                if (child.main_size - child.max_main_size).abs() < MIN_SIZE {
+                    to_resize_children.remove(i);
+                }
+            }
+        }
+    }
+}
+
+fn distribute_fill_across_space(layout_axis: &LayoutAxis, tracks: &mut Vec<TrackData>) {
+    let total_across_size = tracks.iter().map(|t| t.across_size).sum::<f32>()
+        + (tracks.len() - 1) as f32 * layout_axis.gap_across;
+    let mut left_space = layout_axis.across_space() - total_across_size;
+    let mut to_resize_tracks: Vec<&mut TrackData> = Vec::new();
+
+    for track in tracks.iter_mut() {
+        if track.is_fill_across && track.across_size < track.max_across_size {
+            to_resize_tracks.push(track);
+        }
+    }
+
+    while left_space > MIN_SIZE && !to_resize_tracks.is_empty() {
+        let current = left_space / to_resize_tracks.len() as f32;
+        for i in (0..to_resize_tracks.len()).rev() {
+            let track = &mut to_resize_tracks[i];
+            let delta =
+                f32::min(track.max_across_size, track.across_size + current) - track.across_size;
+            track.across_size = track.across_size + delta;
+            left_space = left_space - delta;
+
+            if (track.across_size - track.max_across_size).abs() < MIN_SIZE {
+                to_resize_tracks.remove(i);
+            }
+        }
+    }
+
+    // After assigning the across size to the tracks we can assing the size to the shapes
+    for track in tracks.iter_mut() {
+        if !track.is_fill_across {
+            continue;
+        }
+
+        for child in track.shapes.iter_mut() {
+            if child.is_fill_across {
+                child.across_size = track
+                    .across_size
+                    .clamp(child.min_across_size, child.max_across_size);
+            }
+        }
+    }
+}
+
+fn stretch_tracks_sizes(
+    layout_axis: &LayoutAxis,
+    tracks: &mut Vec<TrackData>,
+    total_across_size: f32,
+) {
+    let total_across_size = total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across;
+    let left_space = layout_axis.across_space() - total_across_size;
+    let delta = left_space / tracks.len() as f32;
+
+    for track in tracks.iter_mut() {
+        track.across_size = track.across_size + delta;
+    }
+}
+
+fn calculate_track_positions(
+    layout_data: &LayoutData,
+    layout_axis: &LayoutAxis,
+    layout_bounds: &Bounds,
+    tracks: &mut Vec<TrackData>,
+    total_across_size: f32,
+) {
+    let mut align_content = &layout_data.align_content;
+
+    if layout_axis.is_auto_across {
+        align_content = &AlignContent::Start;
+    }
+
+    match align_content {
+        AlignContent::End => {
+            let total_across_size_gap: f32 =
+                total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across;
+
+            let delta =
+                layout_axis.across_size - total_across_size_gap - layout_axis.padding_across_end;
+            let mut next_anchor = layout_bounds.nw + layout_axis.across_v * delta;
+
+            for track in tracks.iter_mut() {
+                track.anchor = next_anchor;
+                next_anchor = next_anchor
+                    + layout_axis.across_v * (track.across_size + layout_axis.gap_across);
+            }
+        }
+
+        AlignContent::Center => {
+            let total_across_size_gap: f32 =
+                total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across;
+            let center_margin = (layout_axis.across_size - total_across_size_gap) / 2.0;
+
+            let mut next_anchor = layout_bounds.nw + layout_axis.across_v * center_margin;
+
+            for track in tracks.iter_mut() {
+                track.anchor = next_anchor;
+                next_anchor = next_anchor
+                    + layout_axis.across_v * (track.across_size + layout_axis.gap_across);
+            }
+        }
+
+        AlignContent::SpaceBetween => {
+            let mut next_anchor =
+                layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start;
+
+            let effective_gap = f32::max(
+                layout_axis.gap_across,
+                (layout_axis.across_space() - total_across_size) / (tracks.len() - 1) as f32,
+            );
+
+            for track in tracks.iter_mut() {
+                track.anchor = next_anchor;
+                next_anchor =
+                    next_anchor + layout_axis.across_v * (track.across_size + effective_gap);
+            }
+        }
+
+        AlignContent::SpaceAround => {
+            let effective_gap =
+                (layout_axis.across_space() - total_across_size) / tracks.len() as f32;
+
+            let mut next_anchor = layout_bounds.nw
+                + layout_axis.across_v * (layout_axis.padding_across_start + effective_gap / 2.0);
+
+            for track in tracks.iter_mut() {
+                track.anchor = next_anchor;
+                next_anchor =
+                    next_anchor + layout_axis.across_v * (track.across_size + effective_gap);
+            }
+        }
+
+        AlignContent::SpaceEvenly => {
+            let effective_gap =
+                (layout_axis.across_space() - total_across_size) / (tracks.len() + 1) as f32;
+
+            let mut next_anchor = layout_bounds.nw
+                + layout_axis.across_v * (layout_axis.padding_across_start + effective_gap);
+
+            for track in tracks.iter_mut() {
+                track.anchor = next_anchor;
+                next_anchor =
+                    next_anchor + layout_axis.across_v * (track.across_size + effective_gap);
+            }
+        }
+
+        _ => {
+            let mut next_anchor =
+                layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start;
+
+            for track in tracks.iter_mut() {
+                track.anchor = next_anchor;
+                next_anchor = next_anchor
+                    + layout_axis.across_v * (track.across_size + layout_axis.gap_across);
+            }
+        }
+    }
+}
+
+fn calculate_track_data(
+    shape: &Shape,
+    layout_data: &LayoutData,
+    flex_data: &FlexData,
+    layout_bounds: &Bounds,
+    shapes: &HashMap<Uuid, Shape>,
+    bounds: &HashMap<Uuid, Bounds>,
+) -> Vec<TrackData> {
+    let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
+    let mut tracks = initialize_tracks(shape, &layout_axis, layout_data, flex_data, shapes, bounds);
+
+    if !layout_axis.is_auto_main {
+        distribute_fill_main_space(&layout_axis, &mut tracks);
+    }
+
+    if !layout_axis.is_auto_across {
+        distribute_fill_across_space(&layout_axis, &mut tracks);
+    }
+
+    let total_across_size = tracks.iter().map(|t| t.across_size).sum::<f32>();
+
+    if !layout_axis.is_auto_across && layout_data.align_content == AlignContent::Stretch {
+        stretch_tracks_sizes(&layout_axis, &mut tracks, total_across_size);
+    }
+
+    calculate_track_positions(
+        &layout_data,
+        &layout_axis,
+        layout_bounds,
+        &mut tracks,
+        total_across_size,
+    );
+    tracks
+}
+
+fn first_anchor(
+    layout_data: &LayoutData,
+    layout_axis: &LayoutAxis,
+    track: &TrackData,
+    total_shapes_size: f32,
+) -> Point {
+    if layout_axis.is_auto_main {
+        return track.anchor + layout_axis.main_v * layout_axis.padding_main_start;
+    }
+
+    let delta = match layout_data.justify_content {
+        JustifyContent::Center => (layout_axis.main_size - track.main_size) / 2.0,
+        JustifyContent::End => {
+            layout_axis.main_size - layout_axis.padding_main_end - track.main_size
+        }
+        JustifyContent::SpaceAround => {
+            let effective_gap =
+                (layout_axis.main_space() - total_shapes_size) / (track.shapes.len()) as f32;
+            layout_axis.padding_main_end + f32::max(layout_axis.gap_main, effective_gap / 2.0)
+        }
+        JustifyContent::SpaceEvenly => {
+            let effective_gap =
+                (layout_axis.main_space() - total_shapes_size) / (track.shapes.len() + 1) as f32;
+            layout_axis.padding_main_end + f32::max(layout_axis.gap_main, effective_gap)
+        }
+        _ => layout_axis.padding_main_start,
+    };
+    track.anchor + layout_axis.main_v * delta
+}
+
+fn next_anchor(
+    layout_data: &LayoutData,
+    layout_axis: &LayoutAxis,
+    child_axis: &ChildAxis,
+    track: &TrackData,
+    prev_anchor: Point,
+    total_shapes_size: f32,
+) -> Point {
+    let delta = match layout_data.justify_content {
+        JustifyContent::SpaceBetween => {
+            let effective_gap =
+                (layout_axis.main_space() - total_shapes_size) / (track.shapes.len() - 1) as f32;
+            child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
+        }
+        JustifyContent::SpaceAround => {
+            let effective_gap =
+                (layout_axis.main_space() - total_shapes_size) / (track.shapes.len()) as f32;
+            child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
+        }
+        JustifyContent::SpaceEvenly => {
+            let effective_gap =
+                (layout_axis.main_space() - total_shapes_size) / (track.shapes.len() + 1) as f32;
+            child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
+        }
+        _ => child_axis.main_size + layout_axis.gap_main,
+    };
+    prev_anchor + layout_axis.main_v * delta
+}
+
+fn child_position(
+    child: &Shape,
+    shape_anchor: Point,
+    layout_data: &LayoutData,
+    layout_axis: &LayoutAxis,
+    child_axis: &ChildAxis,
+    track: &TrackData,
+) -> Point {
+    let delta = match child.layout_item {
+        Some(LayoutItem {
+            align_self: Some(align_self),
+            ..
+        }) => match align_self {
+            AlignSelf::Center => (track.across_size - child_axis.across_size) / 2.0,
+            AlignSelf::End => track.across_size - child_axis.across_size,
+            _ => 0.0,
+        },
+        _ => match layout_data.align_items {
+            AlignItems::Center => (track.across_size - child_axis.across_size) / 2.0,
+            AlignItems::End => track.across_size - child_axis.across_size,
+            _ => 0.0,
+        },
+    };
+    shape_anchor + layout_axis.across_v * delta
+}
+
+pub fn reflow_flex_layout(
+    shape: &Shape,
+    layout_data: &LayoutData,
+    flex_data: &FlexData,
+    shapes: &HashMap<Uuid, Shape>,
+    bounds: &HashMap<Uuid, Bounds>,
+) -> VecDeque<Modifier> {
+    let mut result = VecDeque::new();
+    let layout_bounds = &bounds.find(&shape);
+    let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
+    let tracks = calculate_track_data(shape, layout_data, flex_data, layout_bounds, shapes, bounds);
+
+    for track in tracks.iter() {
+        let total_shapes_size = track.shapes.iter().map(|s| s.main_size).sum::<f32>();
+        let mut shape_anchor = first_anchor(&layout_data, &layout_axis, track, total_shapes_size);
+
+        for child_axis in track.shapes.iter() {
+            let child_id = child_axis.id;
+            let Some(child) = shapes.get(&child_id) else {
+                continue;
+            };
+
+            let position = child_position(
+                child,
+                shape_anchor,
+                layout_data,
+                &layout_axis,
+                child_axis,
+                track,
+            );
+            let child_bounds = bounds.find(child);
+            let delta_v = Vector::new_points(&child_bounds.nw, &position);
+
+            let (new_width, new_height) = if layout_data.is_row() {
+                (child_axis.main_size, child_axis.across_size)
+            } else {
+                (child_axis.across_size, child_axis.main_size)
+            };
+
+            let mut transform = Matrix::default();
+
+            if (new_width - child_bounds.width()).abs() > MIN_SIZE
+                || (new_height - child_bounds.height()).abs() > MIN_SIZE
+            {
+                transform.post_concat(&math::resize_matrix(
+                    layout_bounds,
+                    &child_bounds,
+                    new_width,
+                    new_height,
+                ));
+            }
+
+            if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE {
+                transform.post_concat(&Matrix::translate(delta_v));
+            }
+
+            result.push_back(Modifier::transform(child.id, transform));
+
+            shape_anchor = next_anchor(
+                &layout_data,
+                &layout_axis,
+                &child_axis,
+                &track,
+                shape_anchor,
+                total_shapes_size,
+            );
+        }
+    }
+
+    if layout_axis.is_auto_across || layout_axis.is_auto_main {
+        let width = layout_bounds.width();
+        let height = layout_bounds.height();
+
+        let auto_across_size = if layout_axis.is_auto_across {
+            tracks.iter().map(|track| track.across_size).sum::<f32>()
+                + (tracks.len() - 1) as f32 * layout_axis.gap_across
+                + layout_axis.padding_main_start
+                + layout_axis.padding_main_end
+        } else {
+            0.0
+        };
+
+        let auto_main_size = if layout_axis.is_auto_main {
+            tracks
+                .iter()
+                .map(|track| {
+                    track.shapes.iter().map(|s| s.main_size).sum::<f32>()
+                        + (track.shapes.len() - 1) as f32 * layout_axis.gap_main
+                })
+                .reduce(f32::max)
+                .unwrap_or(0.01)
+                + layout_axis.padding_across_start
+                + layout_axis.padding_across_end
+        } else {
+            0.0
+        };
+
+        let (scale_width, scale_height) = if layout_data.is_row() {
+            (
+                if layout_axis.is_auto_main {
+                    auto_main_size / width
+                } else {
+                    1.0
+                },
+                if layout_axis.is_auto_across {
+                    auto_across_size / height
+                } else {
+                    1.0
+                },
+            )
+        } else {
+            (
+                if layout_axis.is_auto_across {
+                    auto_across_size / width
+                } else {
+                    1.0
+                },
+                if layout_axis.is_auto_main {
+                    auto_main_size / height
+                } else {
+                    1.0
+                },
+            )
+        };
+
+        let parent_transform = layout_bounds
+            .transform_matrix()
+            .unwrap_or(Matrix::default());
+
+        let parent_transform_inv = &parent_transform.invert().unwrap();
+        let origin = parent_transform_inv.map_point(layout_bounds.nw);
+
+        let mut scale = Matrix::scale((scale_width, scale_height));
+        scale.post_translate(origin);
+        scale.post_concat(&parent_transform);
+        scale.pre_translate(-origin);
+        scale.pre_concat(&parent_transform_inv);
+
+        result.push_back(Modifier::parent(shape.id, scale));
+    }
+    result
+}
diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs
new file mode 100644
index 000000000..26714cbb0
--- /dev/null
+++ b/render-wasm/src/shapes/modifiers/grid_layout.rs
@@ -0,0 +1,15 @@
+use crate::math::Bounds;
+use crate::shapes::{GridData, LayoutData, Modifier, Shape};
+use std::collections::{HashMap, VecDeque};
+use uuid::Uuid;
+
+pub fn reflow_grid_layout(
+    _shape: &Shape,
+    _layout_data: &LayoutData,
+    _grid_data: &GridData,
+    _shapes: &HashMap<Uuid, Shape>,
+    _bounds: &HashMap<Uuid, Bounds>,
+) -> VecDeque<Modifier> {
+    // TODO
+    VecDeque::new()
+}
diff --git a/render-wasm/src/shapes/transform.rs b/render-wasm/src/shapes/transform.rs
index 9808fdf96..bbfd8f77d 100644
--- a/render-wasm/src/shapes/transform.rs
+++ b/render-wasm/src/shapes/transform.rs
@@ -5,16 +5,46 @@ use crate::mem::SerializableResult;
 use crate::utils::{uuid_from_u32_quartet, uuid_to_u32_quartet};
 use skia::Matrix;
 
+#[derive(PartialEq, Debug, Clone)]
+pub enum Modifier {
+    Transform(TransformEntry),
+    Reflow(Uuid),
+}
+
+impl Modifier {
+    pub fn transform(id: Uuid, transform: Matrix) -> Self {
+        Modifier::Transform(TransformEntry::new(id, transform))
+    }
+    pub fn parent(id: Uuid, transform: Matrix) -> Self {
+        Modifier::Transform(TransformEntry::parent(id, transform))
+    }
+    pub fn reflow(id: Uuid) -> Self {
+        Modifier::Reflow(id)
+    }
+}
+
 #[derive(PartialEq, Debug, Clone)]
 #[repr(C)]
 pub struct TransformEntry {
     pub id: Uuid,
     pub transform: Matrix,
+    pub propagate: bool,
 }
 
 impl TransformEntry {
     pub fn new(id: Uuid, transform: Matrix) -> Self {
-        TransformEntry { id, transform }
+        TransformEntry {
+            id,
+            transform,
+            propagate: true,
+        }
+    }
+    pub fn parent(id: Uuid, transform: Matrix) -> Self {
+        TransformEntry {
+            id,
+            transform,
+            propagate: false,
+        }
     }
 }
 
@@ -40,7 +70,7 @@ impl SerializableResult for TransformEntry {
             0.0,
             1.0,
         );
-        TransformEntry { id, transform }
+        TransformEntry::new(id, transform)
     }
 
     fn as_bytes(&self) -> Self::BytesType {
@@ -75,10 +105,10 @@ mod tests {
 
     #[test]
     fn test_serialization() {
-        let entry = TransformEntry {
-            id: uuid!("550e8400-e29b-41d4-a716-446655440000"),
-            transform: Matrix::new_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 1.0),
-        };
+        let entry = TransformEntry::new(
+            uuid!("550e8400-e29b-41d4-a716-446655440000"),
+            Matrix::new_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 1.0),
+        );
 
         let bytes = entry.as_bytes();