diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h
index 346103d3..f47e8db5 100644
--- a/include/sway/tree/arrange.h
+++ b/include/sway/tree/arrange.h
@@ -1,12 +1,16 @@
 #ifndef _SWAY_ARRANGE_H
 #define _SWAY_ARRANGE_H
-#include "sway/desktop/transaction.h"
 
 struct sway_container;
 
-/**
- * Arrange layout for all the children of the given container.
- */
+void arrange_container(struct sway_container *container);
+
+void arrange_workspace(struct sway_container *workspace);
+
+void arrange_output(struct sway_container *output);
+
+void arrange_root(void);
+
 void arrange_windows(struct sway_container *container);
 
 #endif
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index bbebe453..c228979d 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -16,6 +16,7 @@
 #include "log.h"
 #include "config.h"
 #include "sway/config.h"
+#include "sway/desktop/transaction.h"
 #include "sway/input/input-manager.h"
 #include "sway/input/seat.h"
 #include "sway/layers.h"
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index b5a10370..b52dd196 100644
--- a/sway/desktop/render.c
+++ b/sway/desktop/render.c
@@ -660,7 +660,7 @@ static void render_container_tabbed(struct sway_output *output,
 				pstate->swayc_width - width_gap_adjustment - tab_width * i;
 		}
 
-		render_titlebar(output, damage, child, x, cstate->swayc_y, tab_width,
+		render_titlebar(output, damage, child, x, pstate->swayc_y, tab_width,
 				colors, title_texture, marks_texture);
 
 		if (child == current) {
@@ -721,9 +721,9 @@ static void render_container_stacked(struct sway_output *output,
 			marks_texture = view ? view->marks_unfocused : NULL;
 		}
 
-		int y = cstate->swayc_y + titlebar_height * i;
-		render_titlebar(output, damage, child, cstate->swayc_x, y,
-				cstate->swayc_width, colors, title_texture, marks_texture);
+		int y = pstate->swayc_y + titlebar_height * i;
+		render_titlebar(output, damage, child, pstate->swayc_x, y,
+				pstate->swayc_width, colors, title_texture, marks_texture);
 
 		if (child == current) {
 			current_colors = colors;
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index 7d1824f1..587deb0f 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -8,6 +8,7 @@
 #include "log.h"
 #include "sway/decoration.h"
 #include "sway/desktop.h"
+#include "sway/desktop/transaction.h"
 #include "sway/input/input-manager.h"
 #include "sway/input/seat.h"
 #include "sway/server.h"
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 522fddca..175416f3 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -7,6 +7,7 @@
 #include "log.h"
 #include "sway/decoration.h"
 #include "sway/desktop.h"
+#include "sway/desktop/transaction.h"
 #include "sway/input/input-manager.h"
 #include "sway/input/seat.h"
 #include "sway/server.h"
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index 8d67116a..92f20fcc 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -13,27 +13,18 @@
 #include "list.h"
 #include "log.h"
 
-static void apply_horiz_layout(struct sway_container *parent) {
-	size_t num_children = parent->children->length;
-	if (!num_children) {
+static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
+	if (!children->length) {
 		return;
 	}
-	size_t parent_offset = 0;
-	if (parent->parent->layout == L_TABBED) {
-		parent_offset = container_titlebar_height();
-	} else if (parent->parent->layout == L_STACKED) {
-		parent_offset = container_titlebar_height() *
-			parent->parent->children->length;
-	}
-	size_t parent_height = parent->height - parent_offset;
 
 	// Calculate total width of children
 	double total_width = 0;
-	for (size_t i = 0; i < num_children; ++i) {
-		struct sway_container *child = parent->children->items[i];
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
 		if (child->width <= 0) {
-			if (num_children > 1) {
-				child->width = parent->width / (num_children - 1);
+			if (children->length > 1) {
+				child->width = parent->width / (children->length - 1);
 			} else {
 				child->width = parent->width;
 			}
@@ -46,63 +37,48 @@ static void apply_horiz_layout(struct sway_container *parent) {
 	// Resize windows
 	wlr_log(WLR_DEBUG, "Arranging %p horizontally", parent);
 	double child_x = parent->x;
-	for (size_t i = 0; i < num_children; ++i) {
-		struct sway_container *child = parent->children->items[i];
-		wlr_log(WLR_DEBUG,
-				"Calculating arrangement for %p:%d (will scale %f by %f)",
-				child, child->type, child->width, scale);
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
 		child->x = child_x;
-		child->y = parent->y + parent_offset;
+		child->y = parent->y;
 		child->width = floor(child->width * scale);
-		child->height = parent_height;
+		child->height = parent->height;
 		child_x += child->width;
 
 		// Make last child use remaining width of parent
-		if (i == num_children - 1) {
+		if (i == children->length - 1) {
 			child->width = parent->x + parent->width - child->x;
 		}
 		container_add_gaps(child);
 	}
 }
 
-static void apply_vert_layout(struct sway_container *parent) {
-	size_t num_children = parent->children->length;
-	if (!num_children) {
+static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
+	if (!children->length) {
 		return;
 	}
-	size_t parent_offset = 0;
-	if (parent->parent->layout == L_TABBED) {
-		parent_offset = container_titlebar_height();
-	} else if (parent->parent->layout == L_STACKED) {
-		parent_offset =
-			container_titlebar_height() * parent->parent->children->length;
-	}
-	size_t parent_height = parent->height + parent_offset;
 
 	// Calculate total height of children
 	double total_height = 0;
-	for (size_t i = 0; i < num_children; ++i) {
-		struct sway_container *child = parent->children->items[i];
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
 		if (child->height <= 0) {
-			if (num_children > 1) {
-				child->height = parent_height / (num_children - 1);
+			if (children->length > 1) {
+				child->height = parent->height / (children->length - 1);
 			} else {
-				child->height = parent_height;
+				child->height = parent->height;
 			}
 		}
 		container_remove_gaps(child);
 		total_height += child->height;
 	}
-	double scale = parent_height / total_height;
+	double scale = parent->height / total_height;
 
 	// Resize
 	wlr_log(WLR_DEBUG, "Arranging %p vertically", parent);
-	double child_y = parent->y + parent_offset;
-	for (size_t i = 0; i < num_children; ++i) {
-		struct sway_container *child = parent->children->items[i];
-		wlr_log(WLR_DEBUG,
-				"Calculating arrangement for %p:%d (will scale %f by %f)",
-				child, child->type, child->height, scale);
+	double child_y = parent->y;
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
 		child->x = parent->x;
 		child->y = child_y;
 		child->width = parent->width;
@@ -110,28 +86,21 @@ static void apply_vert_layout(struct sway_container *parent) {
 		child_y += child->height;
 
 		// Make last child use remaining height of parent
-		if (i == num_children - 1) {
-			child->height =
-				parent->y + parent_offset + parent_height - child->y;
+		if (i == children->length - 1) {
+			child->height = parent->y + parent->height - child->y;
 		}
 		container_add_gaps(child);
 	}
 }
 
-static void apply_tabbed_or_stacked_layout(struct sway_container *parent) {
-	if (!parent->children->length) {
+static void apply_tabbed_layout(list_t *children, struct wlr_box *parent) {
+	if (!children->length) {
 		return;
 	}
-	size_t parent_offset = 0;
-	if (parent->parent->layout == L_TABBED) {
-		parent_offset = container_titlebar_height();
-	} else if (parent->parent->layout == L_STACKED) {
-		parent_offset =
-			container_titlebar_height() * parent->parent->children->length;
-	}
+	size_t parent_offset = container_titlebar_height();
 	size_t parent_height = parent->height - parent_offset;
-	for (int i = 0; i < parent->children->length; ++i) {
-		struct sway_container *child = parent->children->items[i];
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
 		container_remove_gaps(child);
 		child->x = parent->x;
 		child->y = parent->y + parent_offset;
@@ -141,65 +110,83 @@ static void apply_tabbed_or_stacked_layout(struct sway_container *parent) {
 	}
 }
 
-static void arrange_children_of(struct sway_container *parent);
+static void apply_stacked_layout(list_t *children, struct wlr_box *parent) {
+	if (!children->length) {
+		return;
+	}
+	size_t parent_offset = container_titlebar_height() * children->length;
+	size_t parent_height = parent->height - parent_offset;
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
+		container_remove_gaps(child);
+		child->x = parent->x;
+		child->y = parent->y + parent_offset;
+		child->width = parent->width;
+		child->height = parent_height;
+		container_add_gaps(child);
+	}
+}
 
 static void arrange_floating(list_t *floating) {
 	for (int i = 0; i < floating->length; ++i) {
 		struct sway_container *floater = floating->items[i];
-		if (floater->type == C_VIEW) {
-			view_autoconfigure(floater->sway_view);
-		} else {
-			arrange_children_of(floater);
-		}
-		container_set_dirty(floater);
+		arrange_container(floater);
 	}
 }
 
-static void arrange_children_of(struct sway_container *parent) {
-	if (config->reloading) {
-		return;
-	}
-	wlr_log(WLR_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", parent,
-		parent->name, parent->width, parent->height, parent->x, parent->y);
-
+static void arrange_children(list_t *children,
+		enum sway_container_layout layout, struct wlr_box *parent) {
 	// Calculate x, y, width and height of children
-	switch (parent->layout) {
+	switch (layout) {
 	case L_HORIZ:
-		apply_horiz_layout(parent);
+		apply_horiz_layout(children, parent);
 		break;
 	case L_VERT:
-		apply_vert_layout(parent);
+		apply_vert_layout(children, parent);
 		break;
 	case L_TABBED:
+		apply_tabbed_layout(children, parent);
+		break;
 	case L_STACKED:
-		apply_tabbed_or_stacked_layout(parent);
+		apply_stacked_layout(children, parent);
 		break;
 	case L_NONE:
-		apply_horiz_layout(parent);
+		apply_horiz_layout(children, parent);
 		break;
 	}
 
 	// Recurse into child containers
-	for (int i = 0; i < parent->children->length; ++i) {
-		struct sway_container *child = parent->children->items[i];
-		if (parent->has_gaps && !child->has_gaps) {
-			child->has_gaps = true;
-			child->gaps_inner = parent->gaps_inner;
-			child->gaps_outer = parent->gaps_outer;
-		}
-		if (child->type == C_VIEW) {
-			view_autoconfigure(child->sway_view);
-		} else {
-			arrange_children_of(child);
-		}
-		container_set_dirty(child);
+	for (int i = 0; i < children->length; ++i) {
+		struct sway_container *child = children->items[i];
+		arrange_container(child);
 	}
 }
 
-static void arrange_workspace(struct sway_container *workspace) {
+void arrange_container(struct sway_container *container) {
 	if (config->reloading) {
 		return;
 	}
+	if (container->type == C_VIEW) {
+		view_autoconfigure(container->sway_view);
+		container_set_dirty(container);
+		return;
+	}
+	if (!sway_assert(container->type == C_CONTAINER, "Expected a container")) {
+		return;
+	}
+	struct wlr_box box;
+	container_get_box(container, &box);
+	arrange_children(container->children, container->layout, &box);
+	container_set_dirty(container);
+}
+
+void arrange_workspace(struct sway_container *workspace) {
+	if (config->reloading) {
+		return;
+	}
+	if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
+		return;
+	}
 	struct sway_container *output = workspace->parent;
 	struct wlr_box *area = &output->sway_output->usable_area;
 	wlr_log(WLR_DEBUG, "Usable area for ws: %dx%d@%d,%d",
@@ -241,22 +228,22 @@ static void arrange_workspace(struct sway_container *workspace) {
 		fs->y = workspace->parent->y;
 		fs->width = workspace->parent->width;
 		fs->height = workspace->parent->height;
-		if (fs->type == C_VIEW) {
-			view_autoconfigure(fs->sway_view);
-		} else {
-			arrange_children_of(fs);
-		}
-		container_set_dirty(fs);
+		arrange_container(fs);
 	} else {
+		struct wlr_box box;
+		container_get_box(workspace, &box);
+		arrange_children(workspace->children, workspace->layout, &box);
 		arrange_floating(workspace->sway_workspace->floating);
-		arrange_children_of(workspace);
 	}
 }
 
-static void arrange_output(struct sway_container *output) {
+void arrange_output(struct sway_container *output) {
 	if (config->reloading) {
 		return;
 	}
+	if (!sway_assert(output->type == C_OUTPUT, "Expected an output")) {
+		return;
+	}
 	const struct wlr_box *output_box = wlr_output_layout_get_box(
 			root_container.sway_root->output_layout,
 			output->sway_output->wlr_output);
@@ -273,7 +260,7 @@ static void arrange_output(struct sway_container *output) {
 	}
 }
 
-static void arrange_root() {
+void arrange_root(void) {
 	if (config->reloading) {
 		return;
 	}
@@ -304,12 +291,8 @@ void arrange_windows(struct sway_container *container) {
 		arrange_workspace(container);
 		break;
 	case C_CONTAINER:
-		arrange_children_of(container);
-		container_set_dirty(container);
-		break;
 	case C_VIEW:
-		view_autoconfigure(container->sway_view);
-		container_set_dirty(container);
+		arrange_container(container);
 		break;
 	case C_TYPES:
 		break;
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 04454ab6..1b193944 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -1163,7 +1163,9 @@ void container_add_gaps(struct sway_container *c) {
 		return;
 	}
 
-	c->current_gaps = c->has_gaps ? c->gaps_inner : config->gaps_inner;
+	struct sway_container *ws = container_parent(c, C_WORKSPACE);
+
+	c->current_gaps = ws->has_gaps ? ws->gaps_inner : config->gaps_inner;
 	c->x += c->current_gaps;
 	c->y += c->current_gaps;
 	c->width -= 2 * c->current_gaps;
diff --git a/sway/tree/root.c b/sway/tree/root.c
index 2dd8f9f2..b42371de 100644
--- a/sway/tree/root.c
+++ b/sway/tree/root.c
@@ -3,6 +3,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <wlr/types/wlr_output_layout.h>
+#include "sway/desktop/transaction.h"
 #include "sway/input/seat.h"
 #include "sway/output.h"
 #include "sway/tree/arrange.h"
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 2870d4f5..1a98c5f2 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -13,6 +13,7 @@
 #include "log.h"
 #include "sway/criteria.h"
 #include "sway/commands.h"
+#include "sway/desktop/transaction.h"
 #include "sway/ipc-server.h"
 #include "sway/output.h"
 #include "sway/input/seat.h"
@@ -224,15 +225,13 @@ void view_autoconfigure(struct sway_view *view) {
 	x = y = width = height = 0;
 	double y_offset = 0;
 
-	// In a tabbed or stacked container, the swayc's y is the top of the title
-	// area. We have to offset the surface y by the height of the title bar, and
-	// disable any top border because we'll always have the title bar.
-	if (con->parent->layout == L_TABBED) {
+	// In a tabbed or stacked container, the swayc's y is the bottom of the
+	// title area. We have to disable any top border because the title bar is
+	// rendered by the parent.
+	if (con->parent->layout == L_TABBED || con->parent->layout == L_STACKED) {
+		view->border_top = false;
+	} else {
 		y_offset = container_titlebar_height();
-		view->border_top = false;
-	} else if (con->parent->layout == L_STACKED) {
-		y_offset = container_titlebar_height() * con->parent->children->length;
-		view->border_top = false;
 	}
 
 	enum sway_container_border border = view->border;
@@ -243,17 +242,17 @@ void view_autoconfigure(struct sway_view *view) {
 	switch (border) {
 	case B_NONE:
 		x = con->x;
-		y = con->y + y_offset;
+		y = con->y;
 		width = con->width;
-		height = con->height - y_offset;
+		height = con->height;
 		break;
 	case B_PIXEL:
 		x = con->x + view->border_thickness * view->border_left;
-		y = con->y + view->border_thickness * view->border_top + y_offset;
+		y = con->y + view->border_thickness * view->border_top;
 		width = con->width
 			- view->border_thickness * view->border_left
 			- view->border_thickness * view->border_right;
-		height = con->height - y_offset
+		height = con->height
 			- view->border_thickness * view->border_top
 			- view->border_thickness * view->border_bottom;
 		break;
@@ -263,15 +262,9 @@ void view_autoconfigure(struct sway_view *view) {
 		width = con->width
 			- view->border_thickness * view->border_left
 			- view->border_thickness * view->border_right;
-		if (y_offset) {
-			y = con->y + y_offset;
-			height = con->height - y_offset
-				- view->border_thickness * view->border_bottom;
-		} else {
-			y = con->y + container_titlebar_height();
-			height = con->height - container_titlebar_height()
-				- view->border_thickness * view->border_bottom;
-		}
+		y = con->y + y_offset;
+		height = con->height - y_offset
+			- view->border_thickness * view->border_bottom;
 		break;
 	}