diff --git a/docs/design/layout.md b/docs/design/layout.md
new file mode 100644
index 000000000000..bcfac0d405f1
--- /dev/null
+++ b/docs/design/layout.md
@@ -0,0 +1,372 @@
+# Cross-Platform Layout
+
+This document explains the workings of .NET MAUI's cross-platform layout system. It primarily includes details of the MAUI.Core layer (where most of the layout is implemented), plus some exceptions/customizations in MAUI.Controls which are included for backward compatibility with Xamarin.Forms.
+
+This document is a work in progress.
+
+# The Basics
+
+## LayoutAlignment
+
+Layout alignment controls how a View positions itself in a container. The possible options are `Fill`, `Start`, `Center`, and `End`. `Fill` is the default for all of the Views in the Controls SDK.
+
+`LayoutAlignment` in Core is mostly handled by the `ComputeFrame()` extension method. When a View is being arranged in a Rect, it calls `ComputeFrame()` to determine what its Frame should be. Frames are always in relation to the container.
+
+For ease of migration, Controls still uses the `LayoutOptions` structure, which also includes the (deprecated) "AndExpand" flags. The Core layout system ignores the "AndExpand" flags and translates the Fill/Start/Center/End values from `LayoutOptions` to the `LayoutAlignment` enumeration.
+
+- Note: Controls still supports "AndExpand" on StackLayout (but _not_ VerticalStackLayout or HorizontalStackLayout). It does this by converting the StackLayout at runtime to a single column or single row Grid. Because that's all "AndExpand" was ever really doing in Forms.
+
+### Center
+
+The View is centered within the container along the axis.
+
+### Start/End
+
+The View is aligned with the starting or ending edge of the container along the axis. Vertically, `Start` is the top edge, `End` is the bottom edge. Horizontally, when the `FlowDirection` of the container is LTR, `Start` is the left edge and `End` is the right edge. When the container's `FlowDirection` is RTL, these are reversed.
+
+However, the Core layout code doesn't actually worry about this - it always computes the frames as if the layout were LTR. The underlying platforms do all the work to flip the layouts in the LayoutHandlers and the platform layout views (e.g., LayoutViewGroup and LayoutPanel). So the Core layout engine doesn't have to worry about effective flow direction.
+
+### Fill
+
+If the View does not have an explicit size along the axis, then `Fill` will make the `Frame` be the size of the container in that direction (minus any margins). The `DesiredSize` of the View doesn't matter - `Fill` will stretch/shrink the View to the container size.
+
+If the View _does_ have an explicit size along the axis (i.e, if the `Width` or `Height` is set to something other than `Dimension.Unset`), then `ComputeFrame()` now has a conflict - the user has requested that the View fill a space, but they've also requested an explicit size. In this case, the explicit size wins. The `Frame` will have the explicit size, and if that size is less than the available space the `Frame` will be aligned to the center of the space.
+
+For example, given this layout...
+
+```
+
+
+
+```
+
+...the label will be 100 units wide and centered horizontally. This also applies to Maximum/Minimum sizes along the axis; e.g.,
+
+```
+
+
+
+```
+
+...will also center the Label horizontally, and will limit it to 50 units wide.
+
+
+## Dimension
+
+The layout code in Core relies on several constants set in `Microsoft.Maui.Primitives.Dimension` to avoid any confusion from magic numbers when dealing with the height/width of views.
+
+By default, the height/width of a view in Core's layout is `Dimension.Unset`; this indicates that the view should be whatever size the underlying platform wants given other constraints (container size, etc.). To indicate an explicit, user-set height/width for an `IView`, the `IView.Width` or `IView.Height` need to return an actual number (between `Dimension.Minimum` and `Dimension.Maximum`). The `Dimension.IsExplicitSet()` helper can be used to check for that.
+
+By definition, any IView in the Core layout system has a minimum width/height set (if not otherwise specified, it's zero). Whether a maximum width/height has been set can be checked with `Dimension.IsMaximumSet()`.
+
+In the Controls layer there are some disconnects between these values in order to provide backward compatibility with magic numbers from Forms. So a View can set a `WidthRequest` of -1, and this will ultimately map to `Dimension.Unset`.
+
+## Z-Index
+
+Z-Index as a Core layout concept does not explicitly map to any platform z-index properties. Instead, it relies on the ordering of the platform controls within the layout backing controls on each platform.
+
+For example, in an Android ViewGroup, absent any other Elevation settings, the children of the ViewGroup will be drawn in order; the first child will be the "lowest", with subsequent overlapping children being drawn atop one another. Windows and iOS work similarly.
+
+So in the Core LayoutHandler implementations, the views in the `ILayout` are sorted by their `ZIndex` (preserving their collection order) when they are added to the platform layout control. This ordering is preserved as children are added, removed, or have their `ZIndex` modified. Because controls later in the collection are drawn over controls earlier in the collection, this effectively gives us a z-index without explicitly mapping any properties.
+
+The underlying platforms _do_ have concepts akin to z-index, but they behave differently enough that simple mapping causes side effects that are undesirable for our cross-platform layouts. (Specifically, Android's closest property, `Elevation`, also affects the shadows of elevated views, and causes problems when elevating controls like Button, which utilizes `Elevation` for state effects like "pressed".)
+
+### Example 1
+
+A Grid has 3 children:
+
+ Label1 { ZIndex = 0 }
+ Label2 { ZIndex = 0 }
+ Label3 { ZIndex = 0 }
+
+The LayoutHandler will create a LayoutViewGroup with the children in the order {Label1, Label2, Label3}.
+
+### Example 2
+
+A Grid has 3 children:
+
+ Label1 { ZIndex = 0 }
+ Label2 { ZIndex = 10 }
+ Label3 { ZIndex = 0 }
+
+The LayoutHandler will create a LayoutViewGroup with the children in the order {Label1, Label3, Label2}. Label2 is pushed to the top because it has the highest z-index. The order of Label1 and Label3 with respect to one another is preserved.
+
+## Padding
+
+Padding is space _inside_ of a view around the _content_ of that view. Anything that implements IPadding can have padding. In some cases, the padding is provided by the platform (by mapping the IPadding thickness values to platform padding properties). For cross-platform layouts, the padding is provided by the layout system.
+
+If padding is requested in the layout system it will be provided to the best of the system's ability. This means that the content of a control _will_ be shrunk enough to provide the padding even if that means shrinking the control to its minimum size (possibly zero).
+
+## Margin
+
+Margin is space _outside_ of a view. Margins are handled entirely by the layout system. Like padding, if a margin is requested it will be granted, even if that shrinks the platform view itself to nothing.
+
+Margins are included in the `DesiredSize` that a view reports after measurement.
+
+## Visibility
+
+In MAUI.Core, Visibility has 3 possible values:
+
+- Visible: the item is visible
+- Hidden: the item occupies space, but cannot be seen
+- Collapsed: the item does not occupy space, and cannot be seen
+
+The MAUI.Core built-in layouts understand and honor all three values.
+
+In MAUI.Controls, for backward-compatibility, visibility is limited to two values via the `IsVisible` property. `IsVisble == true` maps to `Visibility.Visible`; `IsVisible == false` maps to `Visibility.Collapsed`.
+
+The mapped implementations of visibility for individual controls in MAUI.Core _should_ all handle `Visibility.Hidden` properly. So while MAUI.Controls does not have the concept of `Visibility.Hidden`, other SDKs can use it if they like. And custom handlers/controls in MAUI.Controls can also make use of it if they need to.
+
+## Layout vs ContentView
+
+A Layout is a list of views with rules and properties defining how to arrange those views within a frame. Examples of Layouts include Grid, AbsoluteLayout, StackLayout, VerticalStackLayout, and HorizontalStackLayout.
+
+A ContentView is responsible for displaying a single view. It takes an input view (the `Content` property) and displays an output view (the `PresentedContent` property). The input view and output view may be the same, or there may be some transformation applied (e.g., using a template). Examples of ContentViews include Page and ScrollView.
+
+Note: Because of some backward-compatibility constraints, in some cases the base classes for the implementations in MAUI.Controls are confusing. So while ScrollView is not a Layout, it does inherit from the Layout class.
+
+## Measure/Arrange
+
+The cross-platform measure/arrange process in MAUI.Core piggybacks on each platform's layout process. Whenever the platform requests the measure or arrangement of a backing control (e.g., a ContentViewGroup, LayoutViewGroup, LayoutPanel, etc.), the backing control calls its `CrossPlatformMeasure()` or `CrossPlatformArrange()` method.
+
+### CrossPlatformMeasure
+
+In a Layout, `CrossPlatformMeasure()` is responsible for calling `IView.Measure()` on each of the Layout's children. In a ContentView, `CrossPlatformMeasure()` is responsible for calling `IView.Measure()` on the ContentView's `PresentedContent` view.
+
+### CrossPlatformArrange
+
+In a Layout, `CrossPlatformArrange()` is responsible for calling `IView.Arrange()` on each of the Layout's children. In a ContentView, `CrossPlatformArrange()` is responsible for calling `IView.Arrange()` on the ContentView's `PresentedContent` view.
+
+## Platform Backing Layouts
+
+All layouts are backed by a single control per platform:
+
+
+
Platform
Control
+
+
iOS
LayoutView
+
Android
LayoutViewGroup
+
WinUI
LayoutPanel
+
+
+
+(If you're familiar with how layout worked in Xamarin.Forms, these backing controls are similar to the `DefaultRenderer` implementations on each platform.)
+
+Layouts are mapped to these controls via a LayoutHandler. The LayoutHandler is responsible for adding the platform controls for each child of the layout to the backing control, keeping them in an appropriate Z-Index order, and mapping the `CrossPlatformMeasure()` and `CrossPlatformArrange()` methods.
+
+When a backing control (e.g., LayoutViewGroup) is being measured (in response to the platform's measure/layout pass), it calls `CrossPlatformMeasure()` on its cross-platform IView. When the backing control is being arranged, it calls `CrossPlatformArrange()`. This is one of the points where control is passed between the platform's layout system and MAUI.Core's layout system.
+
+The backing controls also handle bounds clipping for their layouts, and are capable of handling safe area on platforms which support it.
+
+- Note: at the moment, we only support Safe Area on iOS. We _should_ support it on Android, but we haven't done that yet.
+
+## Platform Backing ContentViews
+
+All content views are backed by a single control per platform:
+
+
+
Platform
Control
+
+
iOS
ContentView
+
Android
ContentViewGroup
+
WinUI
ContentPanel
+
+
+
+ContentViews are mapped to these controls via a ContentViewHandler. The ContentViewHandler is responsible for setting up the backing control, updating the platform content, and mapping the `CrossPlatformArrange()` and `CrossPlatformMeasure()` methods.
+
+As with layouts, the ContentView backing controls handle bounds clipping and safe area.
+
+# Layout Implementation
+
+## ILayout/ILayoutManager
+
+The ILayout interface is composed of a few other interfaces - it's an IView, an IContainer (that is, it's a list of other IViews), an IPadding (it provides padding around its content), and an ISafeAreaView (it's aware of the safe area of the host platform, and is able to either confine itself to the safe area, or ignore the boundaries).
+
+It also provides the `ClipsToBounds` property, which determines whether its child views can be displayed outside of its boundaries, or are clipped at the edges.
+
+The ILayout interface also provides the `CrossPlatformMeasure()` and `CrossPlatfromArrange()` methods; these methods are utilized by the platform backing controls (e.g. LayoutViewGroup) to handle the cross-platform layout work.
+
+Technically speaking, it's possible to implement all of the cross-platform layout logic directly in an implementation of ILayout, and it's possible that some SDKs may choose to do so. But by convention, that work is instead delegated to an implementation of ILayoutManager.
+
+ILayoutManager defines two methods - `Measure()` and `ArrangeChildren()`. By convention, the ILayout implementation can call `ILayoutManager.Measure()` to do the work of `CrossPlatformMeasure()` and `ILayoutManager.ArrangeChildren()` to do the work of `CrossPlatformArrange()`. All of the actual logic to handle cross-platform layout is contained in the ILayoutManager implementation; this allows us to mix/match/reuse implementations, and it allows other SDK implementors to reuse that logic in other layout types if they desire.
+
+In MAUI.Controls, the abstract type `Layout` handles delegating the cross-platform measure and arrange calls to a layout manager. It also contains a predefined overridable method `CreateLayoutManager()` that derived layouts can use to specify the layout manager they want. And it contains logic which can look for an `ILayoutManagerFactory` service implementation. Layout handles most of the work of adding/removing child views, and provides several virtual methods for doing addtional bookkeeping when the collection changes. It also contains logic for notifying the ILayoutHandler implementation of those changes, so the LayoutHandler can do things like adding/removing platform controls and sorting by z-index.
+
+## Customization
+
+The built-in layouts have predefined layout managers to handle their layout. However, it's possible that users may want to define custom layout logic for layouts. There are two ways to do this.
+
+The first is to create a custom layout type (usually a subclass of an existing layout type, or of Layout) and override `CreateLayoutManager()`.
+
+The alternative is to implement `ILayoutManagerFactory`, register the implementation with the application's service provider, and use that to define which layout manager implementation is to be used for each layout type. In this second scenario, it's possible to define a new layout manager for an existing layout type; e.g., a user can provide a custom layout manager for Grid, with different layout rules or other customizations. This is especially useful for situations where users would like to patch in a previously deprecated behavior for a layout. It's also great when you would like a new behavior but don't want to update the type of an existing widely-used layout.
+
+Creating custom layouts and layout managers is relatively easy; [we keep a repo of examples](https://github.com/hartez/CustomLayoutExamples) to help folks get started.
+
+# The Layouts
+
+The following descriptions apply to the default layout manager implementations provided in MAUI.Core and MAUI.Controls.
+
+## VerticalStackLayout/HorizontalStackLayout
+
+VerticalStackLayout and HorizontalStackLayout are very straightforward - they stack their child views next to each other.
+
+In the unconstrained direction (vertically for VSL, horizontally for HSL), they stack their child views one after the other until all of them have been stacked. They are unconstrained in this direction; they will keep stacking even if it takes them past the width or height constraint parameters given to the Measure and Arrange methods. In other words, the effective height constraint of a VerticalStackLayout is always infinity; the effective width constraint of a HorizontalStackLayout is always infinity.
+
+In the other direction, the layout is constrained. For example, a single VerticalStackLayout on a Page will have its width constrained to the Page's width.
+
+Putting a VSL or HSL into a ScrollView with the appropriate scrolling direction will allow the user to view content which exceeds the size of the container.
+
+Both layouts provide a Spacing property; this spacing is applied between all visible items in the layout. Views which have `Visibility.Collapsed` are not displayed and do not count for spacing purposes.
+
+## StackLayout
+
+StackLayout works the same as VerticalStackLayout/HorizontalStackLayout, but it also includes the `Orientation` property. The StackLayout uses this property to determine whether it will render a VerticalStackLayout or a HorizontalStackLayout. Under the hood, the `Orientation` property simply switches the LayoutManager being used.
+
+StackLayout also checks its child views for the presence of any "AndExpand" layout options; if any exist in the relevant direction (e.g., if there are vertical "AndExpand" options and the `Orientation` is set to `Vertical`), then an AndExpandLayoutManager is swapped in. This LayoutManager converts the StackLayout at runtime to a single row/column Grid, with the appropriate row/column definitions to expand the views which require it.
+
+## Grid
+
+Grids subdivide a given area into rows and columns for arranging their child views.
+
+### RowDefinitions and ColumnDefinitions
+
+The set of rows and columns in a Grid is defined by its RowDefinitions and ColumnDefinitions. Rows are defined in order from the top of the Grid downward. A Grid with no explicit RowDefinitions defaults to a single row with height `GridLength.Star`. Columns are defined in order from the left to the right. A Grid with no explicit ColumnDefinitions defaults to a single column with width `GridLength.Star`.
+
+### Spacing
+
+A Grid can specify `ColumnSpacing` and `RowSpacing`. These are `double` values which specify how much empty space the Grid should leave between its rows and columns. Spacing on both axes defaults to zero. Spacing is applied _between_ rows/columns; it is not applied to the outside edges of the Grid. A Grid with a single column or row will not have any spacing applied on that axis.
+
+Empty rows/columns still count for the purposes of spacing - if the row/column does not have any child views in it, the spacing is still applied. For example, the following Grid:
+
+```xaml
+
+```
+
+will measure 10x10, even though it's empty.
+
+### GridLength
+
+Every row and column definition specifies a `GridLength`, which can be of three types:
+
+#### Explicit
+
+A `double` value which specifies the size of the row/column. When specified, the row/column will get this size in the final layout regardless of its content.
+
+#### Auto
+
+The Grid will measure all of the child Views in the row/column and set the height/width large enough to accommodate the largest View. If the row/column has no child Views, its size will be set to zero. The row/column still counts for the purposes of spacing, however.
+
+#### Star
+
+"Star" values indicate a weighted portion of the remaining space available. Star values are specified in the format "[weight]*", where [weight] is a `double` and defaults to 1.0 if not specified.
+
+As an example, take a Grid with `HeightRequest="100"` and `RowDefinitions="*,2*,6*,0.5*,0.5*"`. The available space is 100 units. The sum of the weights is 10 (1 + 2 + 6 + 0.5 + 0.5). So the size of each "*" is 100 / 10 = 10. Therefore, the rows would have heights of 10, 20, 60, 5, and 5 units.
+
+`GridLength.Star` only has meaning when its axis is constrained; the concept of "remaining space" stops making sense when the space is infinite. When a row or column with `GridLength.Star` is measured with a constraint of `double.PositiveInfinity`, it is treated as if it were `GridLength.Auto`.
+
+## AbsoluteLayout
+
+TK
+
+
+
+# The Layout Process
+
+The cross-platform layout process in Maui.Core piggy-backs off of the native layout process on each platform. Generally speaking, _all_ layout work is initiated by the native layout system; the cross-platform layout process kicks in when a layout or content backing control initiates it as a result of being measured/arranged by the native layout system.
+
+The following diagram shows the process when the native layout system wants to measure a backing view:
+
+```mermaid
+sequenceDiagram
+ participant P as Platform
+ participant BV as Backing View
+ participant XV as Cross-Platform View
+ P->>BV: Measure
+ BV->>XV: Cross-Platform Measure
+ Note over XV: Update DesiredSize
+ XV->>BV: DesiredSize
+ Note over BV: Internal bookkeeping (e.g. SetMeasuredDimension())
+ BV->>P: Size
+```
+
+For the purposes of walking through this, let's assume that the cross-platform View being measured is a ContentView which contains a Label. The native platform (e.g., Android) needs to know the size of the ContentView, given some constraints. Let's say 100 units for the width, 200 units for the height.
+
+The platform calls `Measure()` on the ContentView's backing view (e.g., on Android this would be a `ContentViewGroup`) with the constraints. The backing view converts those constraints to cross-platform units (if necessary), and then calls its `CrossPlatformMeasure()` method with those constraints to determine how large the content (the Label in this case) would like to be.
+
+The `CrossPlatformMeasure()` method is responsible for calling the Label's `Measure()` method. The Label measures its native counterpart (more on that later), and updates its `DesiredSize` property based on that measurement. That value is returned to to the backing view as the result of `CrossPlatformMeasure()`. The backing view does whatever internal bookkeeping it needs to, then returns its measured size to the platform.
+
+## Measuring a Layout
+
+The process for Layout measurement is basically the same as described above, except that multiple child views need to be measured:
+
+```mermaid
+sequenceDiagram
+ participant P as Platform
+ participant BV as Layout Backing View
+ participant XV as Cross-Platform Layout
+ participant C as Child View
+ P->>BV: Measure
+ BV->>XV: Cross-Platform Measure
+ loop Each Child
+ XV->>C: Measure
+ C->>XV: DesiredSize
+ end
+ Note over XV: Update DesiredSize
+ XV->>BV: DesiredSize
+ Note over BV: Internal bookkeeping (e.g. SetMeasuredDimension())
+ BV->>P: Size
+```
+
+The process of iterating over the child views and measuring each one is generally handled by the LayoutManager for each type of Layout, inside of the LayoutManager's `Measure()` method.
+
+## Measuring a View
+
+Any time a cross-platform View is measured, it hands off the actual measurement to its native counterpart.
+
+For instance, in the example above, the Label's `Measure()` method takes the constraints it is given by `CrossPlatformMeasure()` and makes any appropriate adjustments (for example, subtracting its margins). It then hands off the updated constraints to its Handler's `GetDesiredSize()` method. The Handler is aware of the native control (a TextView, on Android); the Handler takes care of converting the constraints to appropriate values for the platform and calling the native control's version of `Measure()`. The Handler takes the return value from the native measurement and converts it back to cross-platform values (if necessary) and returns it to the Label.
+
+The Label adjusts the result if necessary (for example, by adding back in the size of its margins), and then tracks that result in its `DesiredSize` property. It then returns that value as the result of `Measure()`.
+
+```mermaid
+sequenceDiagram
+ participant V as View
+ participant H as Handler
+ participant NV as Native View
+ Note over V: Adjust for margins
+ V->>H: GetDesiredSize
+ Note over H: Convert to native
+ H->>NV: Measure
+ NV->>H: Size
+ Note over H: Convert to x-plat
+ H->>V: Size
+ Note over V: Adjust for margins, record DesiredSize
+```
+
+## Other Notes
+
+Each platform handles layout slightly differently; one of the goals of the cross-platform layout code is to be as platform-agnostic as possible. All special-case scenarios should be dealt with in the handler platform code for each platform. `#if` in cross-platform layout code is a code smell; if it's _ever_ necessary (and it probably shouldn't be), it needs to have a _lot_ of explanation.
+
+As a general rule, any layout pass should call `Measure()` before calling `Arrange()`. It's perfectly legal to call `Measure()` multiple times before calling `Arrange()`; a platform may need to do some speculative measurements before arranging views.
+
+It is also legal to call `Arrange()` multiple times at different sizes/locations as long as `Measure()` has been called at least once. For example, a desktop application may determine that a window resizing operation requires arranging a view at a different location, but that the changes to the window size could not have affected the measurements of the view - there's no reason to re-measure the view.
+
+Each platform generally handles its own optimization of measurement operations; the platform code is much better at making such decisions than the cross-platform layout code ever could be. The goal of the cross-platform code should be to get out of the way and allow the platform to do its own optimization. For example, if the cross-platform code calls `Measure()` on an Android view twice in a row with the same `measureSpec` values, the native Android code will simply return the cached value unless it determines that there's a good underlying reason for the native view to be remeasured. Attempting to make that decision (or cache the measurement value) at the cross-platform level defeats the native decision making (which has far superior local knowledge).
+
+# Platform Stuff
+
+## Measurement on Android
+
+### MeasureSpec
+
+When you see native Android measurement methods, usually they take parameters called `widthMeasureSpec` and `heightMeasureSpec`. You may notice that these tend are integer values. It is very important to note that these are not equivalent to the `double widthConstraint` and `double heightConstraint` values that we use for cross-platform measurement in MAUI. Rather, these are an [Android construct](https://developer.android.com/reference/android/view/View.MeasureSpec) - a packed integer value that includes _both_ the size of the constraint and the type of constraint.
+
+The mode of the measureSpec (that is, the type of constraint), is packed into the upper bits of the integer. The possible options are `UNSPECIFIED`, `EXACTLY`, and `AT_MOST`. See https://developer.android.com/reference/android/view/View.MeasureSpec for more info on what those values mean.
+
+The size of the measureSpec is in the rest of the bits. We have convenience methods in the `MeasureSpec` class for extracting both the size of the spec and the mode.
+
+So even though a `measureSpec` is an integer, you cannot simply add or subtract values from it. If you want to increase or decrease the measurement constraints, you need to unpack the size, make your changes, and pack that into a new measureSpec using `MeasureSpec.MakeMeasureSpec()`. If you try to simply add or subtract values from a measureSpec, the nature of integers and integer overflow will result in nonsense values.
+
+It is also important to remember that measureSpec size values should always be in native pixels. This means that when constructing a measureSpec from cross-platform sizes, you need to convert the sizes using `Context.ToPixels()`.
+
+