From c3ec6a73638fb9cc18499b5ed5a2b7c73a75a5d1 Mon Sep 17 00:00:00 2001
From: Patrik Svensson <patriksvensson@users.noreply.github.com>
Date: Tue, 15 Nov 2022 10:12:17 +0100
Subject: [PATCH] Add Layout widget (#1041)

* Add width to panels
* Add height to panels
* Replace RenderContext with RenderOptions
* Remove exclusivity from alternative buffer
* Add Layout widget
* Add Align widget
---
 examples/Console/Borders/Program.cs           |   2 +-
 examples/Console/Canvas/Program.cs            |   2 +-
 examples/Console/Colors/Program.cs            |   8 +-
 examples/Console/Exceptions/Program.cs        |   8 +-
 examples/Console/Figlet/Program.cs            |   4 +-
 examples/Console/Layout/Layout.csproj         |  15 +
 examples/Console/Layout/Program.cs            |  60 ++++
 examples/Console/Panels/Program.cs            |   4 +-
 examples/Console/Paths/Program.cs             |   4 +-
 examples/Console/Prompt/Program.cs            |   4 +-
 examples/Console/Rules/Program.cs             |   6 +-
 examples/Examples.sln                         |  14 +
 examples/Shared/ColorBox.cs                   |   4 +-
 src/Spectre.Console.Cli/Internal/Composer.cs  |   8 +-
 src/Spectre.Console.ImageSharp/CanvasImage.cs |   6 +-
 .../TestCapabilities.cs                       |  14 +-
 .../TestConsoleExtensions.cs                  |  25 ++
 src/Spectre.Console.v3.ncrunchsolution        |   6 +
 .../Extensions/AlignExtensions.cs             | 106 ++++++
 .../Extensions/AlignableExtensions.cs         |   2 +-
 .../AnsiConsoleExtensions.Screen.cs           |  17 +-
 .../Extensions/HasJustificationExtensions.cs  |  80 +++++
 .../Extensions/LayoutExtensions.cs            |  58 ++++
 .../Extensions/PanelExtensions.cs             |   4 +-
 .../Extensions/RenderOptionsExtensions.cs     |  10 +
 .../Extensions/RenderableExtensions.cs        |   4 +-
 .../Extensions/VisibilityExtensions.cs        |  43 +++
 src/Spectre.Console/HorizontalAlignment.cs    |  22 ++
 src/Spectre.Console/IAlignable.cs             |   2 +-
 src/Spectre.Console/IHasJustification.cs      |  12 +
 src/Spectre.Console/IHasVisibility.cs         |  13 +
 src/Spectre.Console/IRenderable.cs            |   8 +-
 src/Spectre.Console/Internal/Aligner.cs       |  47 +++
 .../Internal/IRatioResolvable.cs              |  22 ++
 .../Internal/Polyfill/IsExternalInit.cs       |  15 +
 src/Spectre.Console/Internal/Ratio.cs         |  72 ++++
 .../Internal/Text/Encoding/HtmlEncoder.cs     |   2 +-
 .../Internal/Text/Encoding/TextEncoder.cs     |   2 +-
 src/Spectre.Console/Justify.cs                |   4 +-
 .../Live/LiveDisplayRenderer.cs               |   2 +-
 src/Spectre.Console/Live/LiveRenderable.cs    |  14 +-
 .../Live/Progress/Columns/DownloadedColumn.cs |   2 +-
 .../Progress/Columns/ElapsedTimeColumn.cs     |   4 +-
 .../Live/Progress/Columns/PercentageColumn.cs |   6 +-
 .../Progress/Columns/ProgressBarColumn.cs     |   2 +-
 .../Progress/Columns/RemainingTimeColumn.cs   |   4 +-
 .../Live/Progress/Columns/SpinnerColumn.cs    |  16 +-
 .../Progress/Columns/TaskDescriptionColumn.cs |   4 +-
 .../Progress/Columns/TransferSpeedColumn.cs   |   2 +-
 .../Live/Progress/ProgressColumn.cs           |   8 +-
 .../Live/Progress/ProgressRenderer.cs         |   2 +-
 .../Renderers/DefaultProgressRenderer.cs      |   4 +-
 .../Renderers/FallbackProgressRenderer.cs     |   2 +-
 .../Renderers/FallbackStatusRenderer.cs       |   2 +-
 .../Prompts/List/ListPromptRenderHook.cs      |   2 +-
 src/Spectre.Console/Region.cs                 |  43 +++
 src/Spectre.Console/Rendering/IRenderHook.cs  |   4 +-
 .../Rendering/JustInTimeRenderable.cs         |   4 +-
 .../Rendering/RenderContext.cs                |  78 -----
 .../Rendering/RenderOptions.cs                |  63 ++++
 .../Rendering/RenderPipeline.cs               |   6 +-
 src/Spectre.Console/Rendering/Renderable.cs   |  16 +-
 src/Spectre.Console/Rendering/Segment.cs      |  37 ++-
 src/Spectre.Console/Rendering/SegmentShape.cs |   9 +-
 src/Spectre.Console/Size.cs                   |  29 ++
 src/Spectre.Console/VerticalAlignment.cs      |  22 ++
 src/Spectre.Console/Widgets/Align.cs          | 146 ++++++++
 src/Spectre.Console/Widgets/Calendar.cs       |   3 +
 src/Spectre.Console/Widgets/Canvas.cs         |   4 +-
 .../Widgets/Charts/BarChart.cs                |   8 +-
 .../Widgets/Charts/BreakdownBar.cs            |   4 +-
 .../Widgets/Charts/BreakdownChart.cs          |  20 +-
 .../Widgets/Charts/BreakdownTags.cs           |   8 +-
 src/Spectre.Console/Widgets/Columns.cs        |  10 +-
 src/Spectre.Console/Widgets/ControlCode.cs    |   6 +-
 .../Widgets/Figlet/FigletText.cs              |  14 +-
 src/Spectre.Console/Widgets/Grid.cs           |   1 +
 src/Spectre.Console/Widgets/Layout/Layout.cs  | 311 ++++++++++++++++++
 .../Widgets/Layout/LayoutPlaceholder.cs       |  31 ++
 .../Widgets/Layout/LayoutRender.cs            |  14 +
 .../Widgets/Layout/LayoutSplitter.cs          |  48 +++
 src/Spectre.Console/Widgets/Markup.cs         |  16 +-
 src/Spectre.Console/Widgets/Padder.cs         |  10 +-
 src/Spectre.Console/Widgets/Panel.cs          |  89 +++--
 src/Spectre.Console/Widgets/PanelHeader.cs    |   8 +-
 src/Spectre.Console/Widgets/Paragraph.cs      |  20 +-
 src/Spectre.Console/Widgets/ProgressBar.cs    |  20 +-
 src/Spectre.Console/Widgets/Rows.cs           |   8 +-
 src/Spectre.Console/Widgets/Rule.cs           |  30 +-
 src/Spectre.Console/Widgets/Table/Table.cs    |  19 +-
 .../Widgets/Table/TableAccessor.cs            |   4 +-
 .../Widgets/Table/TableMeasurer.cs            |   2 +-
 .../Widgets/Table/TableRenderer.cs            |   4 +-
 .../Widgets/Table/TableRendererContext.cs     |   7 +-
 src/Spectre.Console/Widgets/Text.cs           |  16 +-
 src/Spectre.Console/Widgets/TextPath.cs       |  20 +-
 src/Spectre.Console/Widgets/Tree.cs           |  20 +-
 .../Spectre.Console.Cli.Tests.csproj          |  14 +-
 .../Align/Center_Bottom.Output.verified.txt   |  15 +
 .../Align/Center_Middle.Output.verified.txt   |  15 +
 .../Align/Center_Top.Output.verified.txt      |  15 +
 .../Align/Left_Bottom.Output.verified.txt     |  15 +
 .../Align/Left_Middle.Output.verified.txt     |  15 +
 .../Align/Left_Top.Output.verified.txt        |  15 +
 .../Align/Right_Bottom.Output.verified.txt    |  15 +
 .../Align/Right_Middle.Output.verified.txt    |  15 +
 .../Align/Right_Top.Output.verified.txt       |  15 +
 .../Render_Empty_Layout.Output.verified.txt   |  15 +
 ...Render_Fallback_Layout.Output.verified.txt |  15 +
 .../Layout/Render_Layout.Output.verified.txt  |  15 +
 ...er_Layout_With_Columns.Output.verified.txt |  15 +
 ...ut_With_Nested_Columns.Output.verified.txt |  15 +
 ...ayout_With_Nested_Rows.Output.verified.txt |  15 +
 ...ested_Rows_And_Columns.Output.verified.txt |  15 +
 ...espect_To_Minimum_Size.Output.verified.txt |  15 +
 ..._With_Respect_To_Ratio.Output.verified.txt |  15 +
 ...t_With_Respect_To_Size.Output.verified.txt |  15 +
 ...ender_Layout_With_Rows.Output.verified.txt |  15 +
 ...out_Invisible_Children.Output.verified.txt |  15 +
 .../Panel/Render_Height.Output.verified.txt   |  25 ++
 .../Panel/Render_Width.Output.verified.txt    |   3 +
 .../Render_Width_Height.Output.verified.txt   |  25 ++
 .../Render_Width_MaxWidth.Output.verified.txt |   3 +
 .../Render_Centered.Align_Widget.verified.txt |   6 +
 ...nder_LeftAligned.Align_Widget.verified.txt |   6 +
 ...der_RightAligned.Align_Widget.verified.txt |   6 +
 .../Spectre.Console.Tests.csproj              |   3 +-
 .../Live/Progress/ProgressColumnFixture.cs    |   2 +-
 .../Unit/Rendering/RenderHookTests.cs         |   2 +-
 .../Unit/Widgets/AlignTests.cs                | 155 +++++++++
 .../Unit/Widgets/FigletTests.cs               |   6 +-
 .../Unit/Widgets/LayoutTests.cs               | 268 +++++++++++++++
 .../Unit/Widgets/PanelTests.cs                |  76 ++++-
 .../Unit/Widgets/RuleTests.cs                 |   4 +-
 .../Unit/Widgets/Table/TableTests.cs          |  62 +++-
 .../Unit/Widgets/TextPathTests.cs             |   2 +-
 .../Unit/Widgets/TextTests.cs                 |   8 +-
 137 files changed, 2651 insertions(+), 387 deletions(-)
 create mode 100644 examples/Console/Layout/Layout.csproj
 create mode 100644 examples/Console/Layout/Program.cs
 create mode 100644 src/Spectre.Console.v3.ncrunchsolution
 create mode 100644 src/Spectre.Console/Extensions/AlignExtensions.cs
 create mode 100644 src/Spectre.Console/Extensions/HasJustificationExtensions.cs
 create mode 100644 src/Spectre.Console/Extensions/LayoutExtensions.cs
 create mode 100644 src/Spectre.Console/Extensions/RenderOptionsExtensions.cs
 create mode 100644 src/Spectre.Console/Extensions/VisibilityExtensions.cs
 create mode 100644 src/Spectre.Console/HorizontalAlignment.cs
 create mode 100644 src/Spectre.Console/IHasJustification.cs
 create mode 100644 src/Spectre.Console/IHasVisibility.cs
 create mode 100644 src/Spectre.Console/Internal/IRatioResolvable.cs
 create mode 100644 src/Spectre.Console/Internal/Polyfill/IsExternalInit.cs
 create mode 100644 src/Spectre.Console/Region.cs
 delete mode 100644 src/Spectre.Console/Rendering/RenderContext.cs
 create mode 100644 src/Spectre.Console/Rendering/RenderOptions.cs
 create mode 100644 src/Spectre.Console/Size.cs
 create mode 100644 src/Spectre.Console/VerticalAlignment.cs
 create mode 100644 src/Spectre.Console/Widgets/Align.cs
 create mode 100644 src/Spectre.Console/Widgets/Layout/Layout.cs
 create mode 100644 src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
 create mode 100644 src/Spectre.Console/Widgets/Layout/LayoutRender.cs
 create mode 100644 src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Bottom.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Middle.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Top.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Bottom.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Middle.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Top.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Bottom.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Middle.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Top.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Empty_Layout.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Fallback_Layout.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Columns.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Columns.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows_And_Columns.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Minimum_Size.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Ratio.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Size.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Rows.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_Without_Invisible_Children.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Height.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_Height.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_MaxWidth.Output.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Centered.Align_Widget.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_LeftAligned.Align_Widget.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_RightAligned.Align_Widget.verified.txt
 create mode 100644 test/Spectre.Console.Tests/Unit/Widgets/AlignTests.cs
 create mode 100644 test/Spectre.Console.Tests/Unit/Widgets/LayoutTests.cs

diff --git a/examples/Console/Borders/Program.cs b/examples/Console/Borders/Program.cs
index 854ef7f..0dca101 100644
--- a/examples/Console/Borders/Program.cs
+++ b/examples/Console/Borders/Program.cs
@@ -84,7 +84,7 @@ public static class Program
     private static void HorizontalRule(string title)
     {
         AnsiConsole.WriteLine();
-        AnsiConsole.Write(new Rule($"[white bold]{title}[/]").RuleStyle("grey").LeftAligned());
+        AnsiConsole.Write(new Rule($"[white bold]{title}[/]").RuleStyle("grey").LeftJustified());
         AnsiConsole.WriteLine();
     }
 }
diff --git a/examples/Console/Canvas/Program.cs b/examples/Console/Canvas/Program.cs
index 57135a3..941d39f 100644
--- a/examples/Console/Canvas/Program.cs
+++ b/examples/Console/Canvas/Program.cs
@@ -40,7 +40,7 @@ public static class Program
     private static void Render(IRenderable canvas, string title)
     {
         AnsiConsole.WriteLine();
-        AnsiConsole.Write(new Rule($"[yellow]{title}[/]").LeftAligned().RuleStyle("grey"));
+        AnsiConsole.Write(new Rule($"[yellow]{title}[/]").LeftJustified().RuleStyle("grey"));
         AnsiConsole.WriteLine();
         AnsiConsole.Write(canvas);
     }
diff --git a/examples/Console/Colors/Program.cs b/examples/Console/Colors/Program.cs
index 8ee60db..946cb3d 100644
--- a/examples/Console/Colors/Program.cs
+++ b/examples/Console/Colors/Program.cs
@@ -23,7 +23,7 @@ public static class Program
         {
             AnsiConsole.ResetColors();
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("[yellow bold underline]3-bit Colors[/]").RuleStyle("grey").LeftAligned());
+            AnsiConsole.Write(new Rule("[yellow bold underline]3-bit Colors[/]").RuleStyle("grey").LeftJustified());
             AnsiConsole.WriteLine();
 
             for (var i = 0; i < 8; i++)
@@ -46,7 +46,7 @@ public static class Program
         {
             AnsiConsole.ResetColors();
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("[yellow bold underline]4-bit Colors[/]").RuleStyle("grey").LeftAligned());
+            AnsiConsole.Write(new Rule("[yellow bold underline]4-bit Colors[/]").RuleStyle("grey").LeftJustified());
             AnsiConsole.WriteLine();
 
             for (var i = 0; i < 16; i++)
@@ -69,7 +69,7 @@ public static class Program
         {
             AnsiConsole.ResetColors();
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("[yellow bold underline]8-bit Colors[/]").RuleStyle("grey").LeftAligned());
+            AnsiConsole.Write(new Rule("[yellow bold underline]8-bit Colors[/]").RuleStyle("grey").LeftJustified());
             AnsiConsole.WriteLine();
 
             for (var i = 0; i < 16; i++)
@@ -96,7 +96,7 @@ public static class Program
         {
             AnsiConsole.ResetColors();
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("[yellow bold underline]24-bit Colors[/]").RuleStyle("grey").LeftAligned());
+            AnsiConsole.Write(new Rule("[yellow bold underline]24-bit Colors[/]").RuleStyle("grey").LeftJustified());
             AnsiConsole.WriteLine();
 
             AnsiConsole.Write(new ColorBox(width: 80, height: 15));
diff --git a/examples/Console/Exceptions/Program.cs b/examples/Console/Exceptions/Program.cs
index d78e647..032908a 100644
--- a/examples/Console/Exceptions/Program.cs
+++ b/examples/Console/Exceptions/Program.cs
@@ -19,17 +19,17 @@ public static class Program
         catch (Exception ex)
         {
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("Default").LeftAligned());
+            AnsiConsole.Write(new Rule("Default").LeftJustified());
             AnsiConsole.WriteLine();
             AnsiConsole.WriteException(ex);
 
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("Compact").LeftAligned());
+            AnsiConsole.Write(new Rule("Compact").LeftJustified());
             AnsiConsole.WriteLine();
             AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
 
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("Compact + Custom colors").LeftAligned());
+            AnsiConsole.Write(new Rule("Compact + Custom colors").LeftJustified());
             AnsiConsole.WriteLine();
             AnsiConsole.WriteException(ex, new ExceptionSettings
             {
@@ -56,7 +56,7 @@ public static class Program
         catch (Exception ex)
         {
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("Async").LeftAligned());
+            AnsiConsole.Write(new Rule("Async").LeftJustified());
             AnsiConsole.WriteLine();
             AnsiConsole.WriteException(ex, ExceptionFormats.ShortenPaths);
         }
diff --git a/examples/Console/Figlet/Program.cs b/examples/Console/Figlet/Program.cs
index cda5ed5..a831014 100644
--- a/examples/Console/Figlet/Program.cs
+++ b/examples/Console/Figlet/Program.cs
@@ -6,8 +6,8 @@ public static class Program
 {
     public static void Main(string[] args)
     {
-        AnsiConsole.Write(new FigletText("Left aligned").LeftAligned().Color(Color.Red));
+        AnsiConsole.Write(new FigletText("Left aligned").LeftJustified().Color(Color.Red));
         AnsiConsole.Write(new FigletText("Centered").Centered().Color(Color.Green));
-        AnsiConsole.Write(new FigletText("Right aligned").RightAligned().Color(Color.Blue));
+        AnsiConsole.Write(new FigletText("Right aligned").RightJustified().Color(Color.Blue));
     }
 }
diff --git a/examples/Console/Layout/Layout.csproj b/examples/Console/Layout/Layout.csproj
new file mode 100644
index 0000000..5bb2daa
--- /dev/null
+++ b/examples/Console/Layout/Layout.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <ExampleTitle>Layout</ExampleTitle>
+    <ExampleDescription>Demonstrates how to use layouts.</ExampleDescription>
+    <ExampleGroup>Widgets</ExampleGroup>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Shared\Shared.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/examples/Console/Layout/Program.cs b/examples/Console/Layout/Program.cs
new file mode 100644
index 0000000..835b614
--- /dev/null
+++ b/examples/Console/Layout/Program.cs
@@ -0,0 +1,60 @@
+using System;
+using Spectre.Console;
+
+namespace Layouts;
+
+public static class Program
+{
+    public static void Main()
+    {
+        var layout = CreateLayout();
+        AnsiConsole.Write(layout);
+
+        Console.ReadKey(true);
+    }
+
+    private static Layout CreateLayout()
+    {
+        var layout = new Layout();
+
+        layout.SplitRows(
+            new Layout("Top")
+                .SplitColumns(
+                    new Layout("Left")
+                        .SplitRows(
+                            new Layout("LeftTop"),
+                            new Layout("LeftBottom")),
+                    new Layout("Right").Ratio(2),
+                    new Layout("RightRight").Size(3)),
+            new Layout("Bottom"));
+
+        layout["LeftBottom"].Update(
+            new Panel("[blink]PRESS ANY KEY TO QUIT[/]")
+                .Expand()
+                .BorderColor(Color.Yellow)
+                .Padding(0, 0));
+
+        layout["Right"].Update(
+            new Panel(
+                new Table()
+                    .AddColumns("[blue]Qux[/]", "[green]Corgi[/]")
+                    .AddRow("9", "8")
+                    .AddRow("7", "6")
+                    .Expand())
+            .Header("A [yellow]Table[/] in a [blue]Panel[/] (Ratio=2)")
+            .Expand());
+
+        layout["RightRight"].Update(
+            new Panel("Explicit-size-is-[yellow]3[/]")
+                .BorderColor(Color.Yellow)
+                .Padding(0, 0));
+
+        layout["Bottom"].Update(
+        new Panel(
+                new FigletText("Hello World"))
+            .Header("Some [green]Figlet[/] text")
+            .Expand());
+
+        return layout;
+    }
+}
diff --git a/examples/Console/Panels/Program.cs b/examples/Console/Panels/Program.cs
index 79d18a8..dc9ecb8 100644
--- a/examples/Console/Panels/Program.cs
+++ b/examples/Console/Panels/Program.cs
@@ -17,7 +17,7 @@ public static class Program
 
         // Left adjusted panel with text
         AnsiConsole.Write(
-            new Panel(new Text("Left adjusted\nLeft").LeftAligned())
+            new Panel(new Text("Left adjusted\nLeft").LeftJustified())
                 .Expand()
                 .SquareBorder()
                 .Header("[red]Left[/]"));
@@ -32,7 +32,7 @@ public static class Program
 
         // Right adjusted, rounded panel with text
         AnsiConsole.Write(
-            new Panel(new Text("Right adjusted\nRight").RightAligned())
+            new Panel(new Text("Right adjusted\nRight").RightJustified())
                 .Expand()
                 .RoundedBorder()
                 .Header("[blue]Right[/]")
diff --git a/examples/Console/Paths/Program.cs b/examples/Console/Paths/Program.cs
index baeac89..ff88429 100644
--- a/examples/Console/Paths/Program.cs
+++ b/examples/Console/Paths/Program.cs
@@ -58,9 +58,9 @@ public static class Program
         var table = new Table().BorderColor(Color.Grey).Title("Aligned").RoundedBorder();
         table.AddColumns("[grey]Alignment[/]", "[grey]Path[/]");
 
-        table.AddRow(new Text("Left"), new TextPath(path).LeftAligned());
+        table.AddRow(new Text("Left"), new TextPath(path).LeftJustified());
         table.AddRow(new Text("Center"), new TextPath(path).Centered());
-        table.AddRow(new Text("Right"), new TextPath(path).RightAligned());
+        table.AddRow(new Text("Right"), new TextPath(path).RightJustified());
 
         AnsiConsole.Write(table);
     }
diff --git a/examples/Console/Prompt/Program.cs b/examples/Console/Prompt/Program.cs
index 07f01cc..58dadac 100644
--- a/examples/Console/Prompt/Program.cs
+++ b/examples/Console/Prompt/Program.cs
@@ -46,7 +46,7 @@ namespace Prompt
 
             // Summary
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned());
+            AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftJustified());
             AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
                 .RoundedBorder()
                 .BorderColor(Color.Grey)
@@ -63,7 +63,7 @@ namespace Prompt
         private static void WriteDivider(string text)
         {
             AnsiConsole.WriteLine();
-            AnsiConsole.Write(new Rule($"[yellow]{text}[/]").RuleStyle("grey").LeftAligned());
+            AnsiConsole.Write(new Rule($"[yellow]{text}[/]").RuleStyle("grey").LeftJustified());
         }
 
         public static bool AskConfirmation()
diff --git a/examples/Console/Rules/Program.cs b/examples/Console/Rules/Program.cs
index 2f158eb..442ed3f 100644
--- a/examples/Console/Rules/Program.cs
+++ b/examples/Console/Rules/Program.cs
@@ -11,14 +11,14 @@ public static class Program
             new Rule()
                 .RuleStyle(Style.Parse("yellow"))
                 .AsciiBorder()
-                .LeftAligned());
+                .LeftJustified());
 
         // Left aligned title
         Render(
             new Rule("[blue]Left aligned[/]")
                 .RuleStyle(Style.Parse("red"))
                 .DoubleBorder()
-                .LeftAligned());
+                .LeftJustified());
 
         // Centered title
         Render(
@@ -31,7 +31,7 @@ public static class Program
         Render(
             new Rule("[red]Right aligned[/]")
                 .RuleStyle(Style.Parse("blue"))
-                .RightAligned());
+                .RightJustified());
     }
 
     private static void Render(Rule rule)
diff --git a/examples/Examples.sln b/examples/Examples.sln
index 317ec66..01a0700 100644
--- a/examples/Examples.sln
+++ b/examples/Examples.sln
@@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paths", "Console\Paths\Path
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\Layout.csproj", "{A9FDE73A-8452-4CA3-B366-3F900597E132}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -507,6 +509,18 @@ Global
 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x64.Build.0 = Release|Any CPU
 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x86.ActiveCfg = Release|Any CPU
 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x86.Build.0 = Release|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x64.Build.0 = Debug|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x86.Build.0 = Debug|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.ActiveCfg = Release|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.Build.0 = Release|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.ActiveCfg = Release|Any CPU
+		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/examples/Shared/ColorBox.cs b/examples/Shared/ColorBox.cs
index ef00787..7438c9d 100644
--- a/examples/Shared/ColorBox.cs
+++ b/examples/Shared/ColorBox.cs
@@ -20,12 +20,12 @@ public sealed class ColorBox : Renderable
         _width = width;
     }
 
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         return new Measurement(1, GetWidth(maxWidth));
     }
 
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         maxWidth = GetWidth(maxWidth);
 
diff --git a/src/Spectre.Console.Cli/Internal/Composer.cs b/src/Spectre.Console.Cli/Internal/Composer.cs
index 2d8964b..e692e90 100644
--- a/src/Spectre.Console.Cli/Internal/Composer.cs
+++ b/src/Spectre.Console.Cli/Internal/Composer.cs
@@ -83,14 +83,14 @@ internal sealed class Composer : IRenderable
         return this;
     }
 
-    public Measurement Measure(RenderContext context, int maxWidth)
+    public Measurement Measure(RenderOptions options, int maxWidth)
     {
-        return ((IRenderable)new Markup(_content.ToString())).Measure(context, maxWidth);
+        return ((IRenderable)new Markup(_content.ToString())).Measure(options, maxWidth);
     }
 
-    public IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        return ((IRenderable)new Markup(_content.ToString())).Render(context, maxWidth);
+        return ((IRenderable)new Markup(_content.ToString())).Render(options, maxWidth);
     }
 
     public override string ToString()
diff --git a/src/Spectre.Console.ImageSharp/CanvasImage.cs b/src/Spectre.Console.ImageSharp/CanvasImage.cs
index 999ad91..aba8b01 100644
--- a/src/Spectre.Console.ImageSharp/CanvasImage.cs
+++ b/src/Spectre.Console.ImageSharp/CanvasImage.cs
@@ -71,7 +71,7 @@ public sealed class CanvasImage : Renderable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         if (PixelWidth < 0)
         {
@@ -88,7 +88,7 @@ public sealed class CanvasImage : Renderable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var image = Image;
 
@@ -138,6 +138,6 @@ public sealed class CanvasImage : Renderable
             }
         }
 
-        return ((IRenderable)canvas).Render(context, maxWidth);
+        return ((IRenderable)canvas).Render(options, maxWidth);
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console.Testing/TestCapabilities.cs b/src/Spectre.Console.Testing/TestCapabilities.cs
index 1064bd5..d4f702b 100644
--- a/src/Spectre.Console.Testing/TestCapabilities.cs
+++ b/src/Spectre.Console.Testing/TestCapabilities.cs
@@ -27,11 +27,17 @@ public sealed class TestCapabilities : IReadOnlyCapabilities
     public bool Unicode { get; set; }
 
     /// <summary>
-    /// Creates a <see cref="RenderContext"/> with the same capabilities as this instace.
+    /// Creates a <see cref="RenderOptions"/> with the same capabilities as this instace.
     /// </summary>
-    /// <returns>A <see cref="RenderContext"/> with the same capabilities as this instace.</returns>
-    public RenderContext CreateRenderContext()
+    /// <param name="console">The console.</param>
+    /// <returns>A <see cref="RenderOptions"/> with the same capabilities as this instace.</returns>
+    public RenderOptions CreateRenderContext(IAnsiConsole console)
     {
-        return new RenderContext(this);
+        if (console is null)
+        {
+            throw new ArgumentNullException(nameof(console));
+        }
+
+        return RenderOptions.Create(console, this);
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console.Testing/TestConsoleExtensions.cs b/src/Spectre.Console.Testing/TestConsoleExtensions.cs
index c2397d0..545414c 100644
--- a/src/Spectre.Console.Testing/TestConsoleExtensions.cs
+++ b/src/Spectre.Console.Testing/TestConsoleExtensions.cs
@@ -52,6 +52,31 @@ public static class TestConsoleExtensions
         return console;
     }
 
+    /// <summary>
+    /// Sets the console height.
+    /// </summary>
+    /// <param name="console">The console.</param>
+    /// <param name="width">The console height.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static TestConsole Height(this TestConsole console, int width)
+    {
+        console.Profile.Height = width;
+        return console;
+    }
+
+    /// <summary>
+    /// Sets the console size.
+    /// </summary>
+    /// <param name="console">The console.</param>
+    /// <param name="size">The console size.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static TestConsole Size(this TestConsole console, Size size)
+    {
+        console.Profile.Width = size.Width;
+        console.Profile.Height = size.Height;
+        return console;
+    }
+
     /// <summary>
     /// Turns on emitting of VT/ANSI sequences.
     /// </summary>
diff --git a/src/Spectre.Console.v3.ncrunchsolution b/src/Spectre.Console.v3.ncrunchsolution
new file mode 100644
index 0000000..10420ac
--- /dev/null
+++ b/src/Spectre.Console.v3.ncrunchsolution
@@ -0,0 +1,6 @@
+<SolutionConfiguration>
+  <Settings>
+    <AllowParallelTestExecution>True</AllowParallelTestExecution>
+    <SolutionConfigured>True</SolutionConfigured>
+  </Settings>
+</SolutionConfiguration>
\ No newline at end of file
diff --git a/src/Spectre.Console/Extensions/AlignExtensions.cs b/src/Spectre.Console/Extensions/AlignExtensions.cs
new file mode 100644
index 0000000..f2c700e
--- /dev/null
+++ b/src/Spectre.Console/Extensions/AlignExtensions.cs
@@ -0,0 +1,106 @@
+namespace Spectre.Console.Extensions;
+
+/// <summary>
+/// Contains extension methods for <see cref="Align"/>.
+/// </summary>
+public static class AlignExtensions
+{
+    /// <summary>
+    /// Sets the width.
+    /// </summary>
+    /// <param name="align">The <see cref="Align"/> object.</param>
+    /// <param name="width">The width, or <c>null</c> for no explicit width.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Align Width(this Align align, int? width)
+    {
+        if (align is null)
+        {
+            throw new ArgumentNullException(nameof(align));
+        }
+
+        align.Width = width;
+        return align;
+    }
+
+    /// <summary>
+    /// Sets the height.
+    /// </summary>
+    /// <param name="align">The <see cref="Align"/> object.</param>
+    /// <param name="height">The height, or <c>null</c> for no explicit height.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Align Height(this Align align, int? height)
+    {
+        if (align is null)
+        {
+            throw new ArgumentNullException(nameof(align));
+        }
+
+        align.Height = height;
+        return align;
+    }
+
+    /// <summary>
+    /// Sets the vertical alignment.
+    /// </summary>
+    /// <param name="align">The <see cref="Align"/> object.</param>
+    /// <param name="vertical">The vertical alignment, or <c>null</c> for no vertical alignment.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Align VerticalAlignment(this Align align, VerticalAlignment? vertical)
+    {
+        if (align is null)
+        {
+            throw new ArgumentNullException(nameof(align));
+        }
+
+        align.Vertical = vertical;
+        return align;
+    }
+
+    /// <summary>
+    /// Sets the <see cref="Align"/> object to be top aligned.
+    /// </summary>
+    /// <param name="align">The <see cref="Align"/> object.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Align TopAligned(this Align align)
+    {
+        if (align is null)
+        {
+            throw new ArgumentNullException(nameof(align));
+        }
+
+        align.Vertical = Console.VerticalAlignment.Top;
+        return align;
+    }
+
+    /// <summary>
+    /// Sets the <see cref="Align"/> object to be middle aligned.
+    /// </summary>
+    /// <param name="align">The <see cref="Align"/> object.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Align MiddleAligned(this Align align)
+    {
+        if (align is null)
+        {
+            throw new ArgumentNullException(nameof(align));
+        }
+
+        align.Vertical = Console.VerticalAlignment.Middle;
+        return align;
+    }
+
+    /// <summary>
+    /// Sets the <see cref="Align"/> object to be bottom aligned.
+    /// </summary>
+    /// <param name="align">The <see cref="Align"/> object.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Align BottomAligned(this Align align)
+    {
+        if (align is null)
+        {
+            throw new ArgumentNullException(nameof(align));
+        }
+
+        align.Vertical = Console.VerticalAlignment.Bottom;
+        return align;
+    }
+}
diff --git a/src/Spectre.Console/Extensions/AlignableExtensions.cs b/src/Spectre.Console/Extensions/AlignableExtensions.cs
index 4efc934..8039f31 100644
--- a/src/Spectre.Console/Extensions/AlignableExtensions.cs
+++ b/src/Spectre.Console/Extensions/AlignableExtensions.cs
@@ -77,4 +77,4 @@ public static class AlignableExtensions
         obj.Alignment = Justify.Right;
         return obj;
     }
-}
\ No newline at end of file
+}
diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs
index f41ba46..e562cba 100644
--- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs
+++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs
@@ -27,19 +27,18 @@ public static partial class AnsiConsoleExtensions
             throw new NotSupportedException("Alternate buffers are not supported by your terminal.");
         }
 
-        console.ExclusivityMode.Run<object?>(() =>
-        {
-            // Switch to alternate screen
-            console.Write(new ControlCode("\u001b[?1049h\u001b[H"));
+        // Switch to alternate screen
+        console.Write(new ControlCode("\u001b[?1049h\u001b[H"));
 
+        try
+        {
             // Execute custom action
             action();
-
+        }
+        finally
+        {
             // Switch back to primary screen
             console.Write(new ControlCode("\u001b[?1049l"));
-
-            // Dummy result
-            return null;
-        });
+        }
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Extensions/HasJustificationExtensions.cs b/src/Spectre.Console/Extensions/HasJustificationExtensions.cs
new file mode 100644
index 0000000..63b8c4d
--- /dev/null
+++ b/src/Spectre.Console/Extensions/HasJustificationExtensions.cs
@@ -0,0 +1,80 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Contains extension methods for <see cref="IHasJustification"/>.
+/// </summary>
+public static class HasJustificationExtensions
+{
+    /// <summary>
+    /// Sets the justification for an <see cref="IHasJustification"/> object.
+    /// </summary>
+    /// <typeparam name="T">The type that can be justified.</typeparam>
+    /// <param name="obj">The alignable object.</param>
+    /// <param name="alignment">The alignment.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static T Justify<T>(this T obj, Justify? alignment)
+        where T : class, IHasJustification
+    {
+        if (obj is null)
+        {
+            throw new System.ArgumentNullException(nameof(obj));
+        }
+
+        obj.Justification = alignment;
+        return obj;
+    }
+
+    /// <summary>
+    /// Sets the <see cref="IHasJustification"/> object to be left justified.
+    /// </summary>
+    /// <typeparam name="T">The type that can be justified.</typeparam>
+    /// <param name="obj">The alignable object.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static T LeftJustified<T>(this T obj)
+        where T : class, IHasJustification
+    {
+        if (obj is null)
+        {
+            throw new System.ArgumentNullException(nameof(obj));
+        }
+
+        obj.Justification = Console.Justify.Left;
+        return obj;
+    }
+
+    /// <summary>
+    /// Sets the <see cref="IHasJustification"/> object to be centered.
+    /// </summary>
+    /// <typeparam name="T">The type that can be justified.</typeparam>
+    /// <param name="obj">The alignable object.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static T Centered<T>(this T obj)
+        where T : class, IHasJustification
+    {
+        if (obj is null)
+        {
+            throw new System.ArgumentNullException(nameof(obj));
+        }
+
+        obj.Justification = Console.Justify.Center;
+        return obj;
+    }
+
+    /// <summary>
+    /// Sets the <see cref="IHasJustification"/> object to be right justified.
+    /// </summary>
+    /// <typeparam name="T">The type that can be justified.</typeparam>
+    /// <param name="obj">The alignable object.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static T RightJustified<T>(this T obj)
+        where T : class, IHasJustification
+    {
+        if (obj is null)
+        {
+            throw new System.ArgumentNullException(nameof(obj));
+        }
+
+        obj.Justification = Console.Justify.Right;
+        return obj;
+    }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/Extensions/LayoutExtensions.cs b/src/Spectre.Console/Extensions/LayoutExtensions.cs
new file mode 100644
index 0000000..6acea7b
--- /dev/null
+++ b/src/Spectre.Console/Extensions/LayoutExtensions.cs
@@ -0,0 +1,58 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Contains extension methods for <see cref="Layout"/>.
+/// </summary>
+public static class LayoutExtensions
+{
+    /// <summary>
+    /// Sets the ratio of the layout.
+    /// </summary>
+    /// <param name="layout">The layout.</param>
+    /// <param name="ratio">The ratio.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Layout Ratio(this Layout layout, int ratio)
+    {
+        if (layout is null)
+        {
+            throw new ArgumentNullException(nameof(layout));
+        }
+
+        layout.Ratio = ratio;
+        return layout;
+    }
+
+    /// <summary>
+    /// Sets the size of the layout.
+    /// </summary>
+    /// <param name="layout">The layout.</param>
+    /// <param name="size">The size.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Layout Size(this Layout layout, int size)
+    {
+        if (layout is null)
+        {
+            throw new ArgumentNullException(nameof(layout));
+        }
+
+        layout.Size = size;
+        return layout;
+    }
+
+    /// <summary>
+    /// Sets the minimum width of the layout.
+    /// </summary>
+    /// <param name="layout">The layout.</param>
+    /// <param name="size">The size.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static Layout MinimumSize(this Layout layout, int size)
+    {
+        if (layout is null)
+        {
+            throw new ArgumentNullException(nameof(layout));
+        }
+
+        layout.MinimumSize = size;
+        return layout;
+    }
+}
diff --git a/src/Spectre.Console/Extensions/PanelExtensions.cs b/src/Spectre.Console/Extensions/PanelExtensions.cs
index cc957e4..d837465 100644
--- a/src/Spectre.Console/Extensions/PanelExtensions.cs
+++ b/src/Spectre.Console/Extensions/PanelExtensions.cs
@@ -24,7 +24,7 @@ public static class PanelExtensions
             throw new ArgumentNullException(nameof(text));
         }
 
-        alignment ??= panel.Header?.Alignment;
+        alignment ??= panel.Header?.Justification;
         return Header(panel, new PanelHeader(text, alignment));
     }
 
@@ -44,7 +44,7 @@ public static class PanelExtensions
         if (panel.Header != null)
         {
             // Update existing style
-            panel.Header.Alignment = alignment;
+            panel.Header.Justification = alignment;
         }
         else
         {
diff --git a/src/Spectre.Console/Extensions/RenderOptionsExtensions.cs b/src/Spectre.Console/Extensions/RenderOptionsExtensions.cs
new file mode 100644
index 0000000..6e501da
--- /dev/null
+++ b/src/Spectre.Console/Extensions/RenderOptionsExtensions.cs
@@ -0,0 +1,10 @@
+namespace Spectre.Console;
+
+internal static class RenderOptionsExtensions
+{
+    public static BoxBorder GetSafeBorder<T>(this RenderOptions options, T border)
+        where T : IHasBoxBorder, IHasBorder
+    {
+        return BoxExtensions.GetSafeBorder(border.Border, !options.Unicode && border.UseSafeBorder);
+    }
+}
diff --git a/src/Spectre.Console/Extensions/RenderableExtensions.cs b/src/Spectre.Console/Extensions/RenderableExtensions.cs
index 9e72be9..4761b2a 100644
--- a/src/Spectre.Console/Extensions/RenderableExtensions.cs
+++ b/src/Spectre.Console/Extensions/RenderableExtensions.cs
@@ -23,13 +23,13 @@ public static class RenderableExtensions
             throw new ArgumentNullException(nameof(renderable));
         }
 
-        var context = new RenderContext(console.Profile.Capabilities);
+        var context = RenderOptions.Create(console, console.Profile.Capabilities);
         var renderables = console.Pipeline.Process(context, new[] { renderable });
 
         return GetSegments(console, context, renderables);
     }
 
-    private static IEnumerable<Segment> GetSegments(IAnsiConsole console, RenderContext options, IEnumerable<IRenderable> renderables)
+    private static IEnumerable<Segment> GetSegments(IAnsiConsole console, RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         var result = new List<Segment>();
         foreach (var renderable in renderables)
diff --git a/src/Spectre.Console/Extensions/VisibilityExtensions.cs b/src/Spectre.Console/Extensions/VisibilityExtensions.cs
new file mode 100644
index 0000000..38bc04b
--- /dev/null
+++ b/src/Spectre.Console/Extensions/VisibilityExtensions.cs
@@ -0,0 +1,43 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Contains extension methods for <see cref="IHasVisibility"/>.
+/// </summary>
+public static class VisibilityExtensions
+{
+    /// <summary>
+    /// Marks the specified object as being invisible.
+    /// </summary>
+    /// <typeparam name="T">An object implementing <see cref="IHasVisibility"/>.</typeparam>
+    /// <param name="obj">The object to hide.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static T Invisible<T>(this T obj)
+        where T : class, IHasVisibility
+    {
+        if (obj is null)
+        {
+            throw new ArgumentNullException(nameof(obj));
+        }
+
+        obj.IsVisible = false;
+        return obj;
+    }
+
+    /// <summary>
+    /// Marks the specified object as being visible.
+    /// </summary>
+    /// <typeparam name="T">An object implementing <see cref="IHasVisibility"/>.</typeparam>
+    /// <param name="obj">The object to show.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public static T Visible<T>(this T obj)
+        where T : class, IHasVisibility
+    {
+        if (obj is null)
+        {
+            throw new ArgumentNullException(nameof(obj));
+        }
+
+        obj.IsVisible = true;
+        return obj;
+    }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/HorizontalAlignment.cs b/src/Spectre.Console/HorizontalAlignment.cs
new file mode 100644
index 0000000..e8f6c21
--- /dev/null
+++ b/src/Spectre.Console/HorizontalAlignment.cs
@@ -0,0 +1,22 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents horizontal alignment.
+/// </summary>
+public enum HorizontalAlignment
+{
+    /// <summary>
+    /// Left aligned.
+    /// </summary>
+    Left,
+
+    /// <summary>
+    /// Centered.
+    /// </summary>
+    Center,
+
+    /// <summary>
+    /// Right aligned.
+    /// </summary>
+    Right,
+}
diff --git a/src/Spectre.Console/IAlignable.cs b/src/Spectre.Console/IAlignable.cs
index 1f4f0c0..6207bfa 100644
--- a/src/Spectre.Console/IAlignable.cs
+++ b/src/Spectre.Console/IAlignable.cs
@@ -9,4 +9,4 @@ public interface IAlignable
     /// Gets or sets the alignment.
     /// </summary>
     Justify? Alignment { get; set; }
-}
\ No newline at end of file
+}
diff --git a/src/Spectre.Console/IHasJustification.cs b/src/Spectre.Console/IHasJustification.cs
new file mode 100644
index 0000000..72b75db
--- /dev/null
+++ b/src/Spectre.Console/IHasJustification.cs
@@ -0,0 +1,12 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents something that has justification.
+/// </summary>
+public interface IHasJustification
+{
+    /// <summary>
+    /// Gets or sets the justification.
+    /// </summary>
+    Justify? Justification { get; set; }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/IHasVisibility.cs b/src/Spectre.Console/IHasVisibility.cs
new file mode 100644
index 0000000..e6bb2e3
--- /dev/null
+++ b/src/Spectre.Console/IHasVisibility.cs
@@ -0,0 +1,13 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents something that can be hidden.
+/// </summary>
+public interface IHasVisibility
+{
+    /// <summary>
+    /// Gets or sets a value indicating whether or not the object should
+    /// be visible or not.
+    /// </summary>
+    bool IsVisible { get; set; }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/IRenderable.cs b/src/Spectre.Console/IRenderable.cs
index af27d38..6e5b281 100644
--- a/src/Spectre.Console/IRenderable.cs
+++ b/src/Spectre.Console/IRenderable.cs
@@ -8,16 +8,16 @@ public interface IRenderable
     /// <summary>
     /// Measures the renderable object.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="maxWidth">The maximum allowed width.</param>
     /// <returns>The minimum and maximum width of the object.</returns>
-    Measurement Measure(RenderContext context, int maxWidth);
+    Measurement Measure(RenderOptions options, int maxWidth);
 
     /// <summary>
     /// Renders the object.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="maxWidth">The maximum allowed width.</param>
     /// <returns>A collection of segments.</returns>
-    IEnumerable<Segment> Render(RenderContext context, int maxWidth);
+    IEnumerable<Segment> Render(RenderOptions options, int maxWidth);
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/Aligner.cs b/src/Spectre.Console/Internal/Aligner.cs
index 92c7ff3..760c7b2 100644
--- a/src/Spectre.Console/Internal/Aligner.cs
+++ b/src/Spectre.Console/Internal/Aligner.cs
@@ -89,4 +89,51 @@ internal static class Aligner
                 throw new NotSupportedException("Unknown alignment");
         }
     }
+
+    public static void AlignHorizontally<T>(T segments, HorizontalAlignment alignment, int maxWidth)
+        where T : List<Segment>
+    {
+        var width = Segment.CellCount(segments);
+        if (width >= maxWidth)
+        {
+            return;
+        }
+
+        switch (alignment)
+        {
+            case HorizontalAlignment.Left:
+                {
+                    var diff = maxWidth - width;
+                    segments.Add(Segment.Padding(diff));
+                    break;
+                }
+
+            case HorizontalAlignment.Right:
+                {
+                    var diff = maxWidth - width;
+                    segments.Insert(0, Segment.Padding(diff));
+                    break;
+                }
+
+            case HorizontalAlignment.Center:
+                {
+                    // Left side.
+                    var diff = (maxWidth - width) / 2;
+                    segments.Insert(0, Segment.Padding(diff));
+
+                    // Right side
+                    segments.Add(Segment.Padding(diff));
+                    var remainder = (maxWidth - width) % 2;
+                    if (remainder != 0)
+                    {
+                        segments.Add(Segment.Padding(remainder));
+                    }
+
+                    break;
+                }
+
+            default:
+                throw new NotSupportedException("Unknown alignment");
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/IRatioResolvable.cs b/src/Spectre.Console/Internal/IRatioResolvable.cs
new file mode 100644
index 0000000..83d89d0
--- /dev/null
+++ b/src/Spectre.Console/Internal/IRatioResolvable.cs
@@ -0,0 +1,22 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents something that can be used to resolve ratios.
+/// </summary>
+internal interface IRatioResolvable
+{
+    /// <summary>
+    /// Gets the ratio.
+    /// </summary>
+    int Ratio { get; }
+
+    /// <summary>
+    /// Gets the size.
+    /// </summary>
+    int? Size { get; }
+
+    /// <summary>
+    /// Gets the minimum size.
+    /// </summary>
+    int MinimumSize { get; }
+}
diff --git a/src/Spectre.Console/Internal/Polyfill/IsExternalInit.cs b/src/Spectre.Console/Internal/Polyfill/IsExternalInit.cs
new file mode 100644
index 0000000..e152620
--- /dev/null
+++ b/src/Spectre.Console/Internal/Polyfill/IsExternalInit.cs
@@ -0,0 +1,15 @@
+#if NETSTANDARD2_0
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices
+{
+    /// <summary>
+    /// Reserved to be used by the compiler for tracking metadata.
+    /// This class should not be used by developers in source code.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    internal static class IsExternalInit
+    {
+    }
+}
+#endif
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/Ratio.cs b/src/Spectre.Console/Internal/Ratio.cs
index a0f3fc9..5865b72 100644
--- a/src/Spectre.Console/Internal/Ratio.cs
+++ b/src/Spectre.Console/Internal/Ratio.cs
@@ -5,6 +5,78 @@ namespace Spectre.Console;
 
 internal static class Ratio
 {
+    public static List<int> Resolve(int total, IEnumerable<IRatioResolvable> edges)
+    {
+        static (int Div, float Mod) DivMod(float x, float y)
+        {
+            return ((int)(x / y), x % y);
+        }
+
+        static int? GetEdgeWidth(IRatioResolvable edge)
+        {
+            if (edge.Size != null && edge.Size < edge.MinimumSize)
+            {
+                return edge.MinimumSize;
+            }
+
+            return edge.Size;
+        }
+
+        var sizes = edges.Select(x => GetEdgeWidth(x)).ToArray();
+
+        while (sizes.Any(s => s == null))
+        {
+            // Get all edges and map them back to their index.
+            // Ignore edges which have a explicit size.
+            var flexibleEdges = sizes.Zip(edges, (a, b) => (Size: a, Edge: b))
+                .Enumerate()
+                .Select(x => (x.Index, x.Item.Size, x.Item.Edge))
+                .Where(x => x.Size == null)
+                .ToList();
+
+            // Get the remaining space
+            var remaining = total - sizes.Select(size => size ?? 0).Sum();
+            if (remaining <= 0)
+            {
+                // No more room for flexible edges.
+                return sizes
+                    .Zip(edges, (size, edge) => (Size: size, Edge: edge))
+                    .Select(zip => zip.Size ?? zip.Edge.MinimumSize)
+                    .Select(size => size > 0 ? size : 1)
+                    .ToList();
+            }
+
+            var portion = (float)remaining / flexibleEdges.Sum(x => Math.Max(1, x.Edge.Ratio));
+
+            var invalidate = false;
+            foreach (var (index, size, edge) in flexibleEdges)
+            {
+                if (portion * edge.Ratio <= edge.MinimumSize)
+                {
+                    sizes[index] = edge.MinimumSize;
+
+                    // New fixed size will invalidate calculations,
+                    // so we need to repeat the process
+                    invalidate = true;
+                    break;
+                }
+            }
+
+            if (!invalidate)
+            {
+                var remainder = 0f;
+                foreach (var flexibleEdge in flexibleEdges)
+                {
+                    var (div, mod) = DivMod((portion * flexibleEdge.Edge.Ratio) + remainder, 1);
+                    remainder = mod;
+                    sizes[flexibleEdge.Index] = div;
+                }
+            }
+        }
+
+        return sizes.Select(x => x ?? 1).ToList();
+    }
+
     public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values)
     {
         ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
diff --git a/src/Spectre.Console/Internal/Text/Encoding/HtmlEncoder.cs b/src/Spectre.Console/Internal/Text/Encoding/HtmlEncoder.cs
index d021e26..31f36a0 100644
--- a/src/Spectre.Console/Internal/Text/Encoding/HtmlEncoder.cs
+++ b/src/Spectre.Console/Internal/Text/Encoding/HtmlEncoder.cs
@@ -4,7 +4,7 @@ internal sealed class HtmlEncoder : IAnsiConsoleEncoder
 {
     public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables)
     {
-        var context = new RenderContext(new EncoderCapabilities(ColorSystem.TrueColor));
+        var context = RenderOptions.Create(console, new EncoderCapabilities(ColorSystem.TrueColor));
         var builder = new StringBuilder();
 
         builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n");
diff --git a/src/Spectre.Console/Internal/Text/Encoding/TextEncoder.cs b/src/Spectre.Console/Internal/Text/Encoding/TextEncoder.cs
index ac2bc61..2570699 100644
--- a/src/Spectre.Console/Internal/Text/Encoding/TextEncoder.cs
+++ b/src/Spectre.Console/Internal/Text/Encoding/TextEncoder.cs
@@ -4,7 +4,7 @@ internal sealed class TextEncoder : IAnsiConsoleEncoder
 {
     public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables)
     {
-        var context = new RenderContext(new EncoderCapabilities(ColorSystem.TrueColor));
+        var context = RenderOptions.Create(console, new EncoderCapabilities(ColorSystem.TrueColor));
         var builder = new StringBuilder();
 
         foreach (var renderable in renderables)
diff --git a/src/Spectre.Console/Justify.cs b/src/Spectre.Console/Justify.cs
index dc81437..a4578de 100644
--- a/src/Spectre.Console/Justify.cs
+++ b/src/Spectre.Console/Justify.cs
@@ -6,12 +6,12 @@ namespace Spectre.Console;
 public enum Justify
 {
     /// <summary>
-    /// Left aligned.
+    /// Left justified.
     /// </summary>
     Left = 0,
 
     /// <summary>
-    /// Right aligned.
+    /// Right justified.
     /// </summary>
     Right = 1,
 
diff --git a/src/Spectre.Console/Live/LiveDisplayRenderer.cs b/src/Spectre.Console/Live/LiveDisplayRenderer.cs
index 3a95df1..a9a1a52 100644
--- a/src/Spectre.Console/Live/LiveDisplayRenderer.cs
+++ b/src/Spectre.Console/Live/LiveDisplayRenderer.cs
@@ -41,7 +41,7 @@ internal sealed class LiveDisplayRenderer : IRenderHook
         }
     }
 
-    public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+    public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         lock (_context.Lock)
         {
diff --git a/src/Spectre.Console/Live/LiveRenderable.cs b/src/Spectre.Console/Live/LiveRenderable.cs
index 9152540..40107f9 100644
--- a/src/Spectre.Console/Live/LiveRenderable.cs
+++ b/src/Spectre.Console/Live/LiveRenderable.cs
@@ -67,7 +67,7 @@ internal sealed class LiveRenderable : Renderable
         }
     }
 
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         lock (_lock)
         {
@@ -75,10 +75,10 @@ internal sealed class LiveRenderable : Renderable
 
             if (_renderable != null)
             {
-                var segments = _renderable.Render(context, maxWidth);
+                var segments = _renderable.Render(options, maxWidth);
                 var lines = Segment.SplitLines(segments);
 
-                var shape = SegmentShape.Calculate(context, lines);
+                var shape = SegmentShape.Calculate(options, lines);
                 if (shape.Height > _console.Profile.Height)
                 {
                     if (Overflow == VerticalOverflow.Crop)
@@ -97,12 +97,12 @@ internal sealed class LiveRenderable : Renderable
                             lines.RemoveRange(0, start);
                         }
 
-                        shape = SegmentShape.Calculate(context, lines);
+                        shape = SegmentShape.Calculate(options, lines);
                     }
                     else if (Overflow == VerticalOverflow.Ellipsis)
                     {
                         var ellipsisText = _console.Profile.Capabilities.Unicode ? "…" : "...";
-                        var ellipsis = new SegmentLine(((IRenderable)new Markup($"[yellow]{ellipsisText}[/]")).Render(context, maxWidth));
+                        var ellipsis = new SegmentLine(((IRenderable)new Markup($"[yellow]{ellipsisText}[/]")).Render(options, maxWidth));
 
                         if (OverflowCropping == VerticalOverflowCropping.Bottom)
                         {
@@ -120,14 +120,14 @@ internal sealed class LiveRenderable : Renderable
                             lines.Insert(0, ellipsis);
                         }
 
-                        shape = SegmentShape.Calculate(context, lines);
+                        shape = SegmentShape.Calculate(options, lines);
                     }
 
                     DidOverflow = true;
                 }
 
                 _shape = _shape == null ? shape : _shape.Value.Inflate(shape);
-                _shape.Value.Apply(context, ref lines);
+                _shape.Value.Apply(options, ref lines);
 
                 foreach (var (_, _, last, line) in lines.Enumerate())
                 {
diff --git a/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs b/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
index fa8e496..6162986 100644
--- a/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
@@ -11,7 +11,7 @@ public sealed class DownloadedColumn : ProgressColumn
     public CultureInfo? Culture { get; set; }
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         var total = new FileSize(task.MaxValue);
 
diff --git a/src/Spectre.Console/Live/Progress/Columns/ElapsedTimeColumn.cs b/src/Spectre.Console/Live/Progress/Columns/ElapsedTimeColumn.cs
index a005be6..a29fdfb 100644
--- a/src/Spectre.Console/Live/Progress/Columns/ElapsedTimeColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/ElapsedTimeColumn.cs
@@ -14,7 +14,7 @@ public sealed class ElapsedTimeColumn : ProgressColumn
     public Style Style { get; set; } = new Style(foreground: Color.Blue);
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         var elapsed = task.ElapsedTime;
         if (elapsed == null)
@@ -31,7 +31,7 @@ public sealed class ElapsedTimeColumn : ProgressColumn
     }
 
     /// <inheritdoc/>
-    public override int? GetColumnWidth(RenderContext context)
+    public override int? GetColumnWidth(RenderOptions options)
     {
         return 8;
     }
diff --git a/src/Spectre.Console/Live/Progress/Columns/PercentageColumn.cs b/src/Spectre.Console/Live/Progress/Columns/PercentageColumn.cs
index f0bf597..0dbef1c 100644
--- a/src/Spectre.Console/Live/Progress/Columns/PercentageColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/PercentageColumn.cs
@@ -16,15 +16,15 @@ public sealed class PercentageColumn : ProgressColumn
     public Style CompletedStyle { get; set; } = new Style(foreground: Color.Green);
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         var percentage = (int)task.Percentage;
         var style = percentage == 100 ? CompletedStyle : Style ?? Style.Plain;
-        return new Text($"{percentage}%", style).RightAligned();
+        return new Text($"{percentage}%", style).RightJustified();
     }
 
     /// <inheritdoc/>
-    public override int? GetColumnWidth(RenderContext context)
+    public override int? GetColumnWidth(RenderOptions options)
     {
         return 4;
     }
diff --git a/src/Spectre.Console/Live/Progress/Columns/ProgressBarColumn.cs b/src/Spectre.Console/Live/Progress/Columns/ProgressBarColumn.cs
index 7b0b018..16c7c19 100644
--- a/src/Spectre.Console/Live/Progress/Columns/ProgressBarColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/ProgressBarColumn.cs
@@ -31,7 +31,7 @@ public sealed class ProgressBarColumn : ProgressColumn
     public Style IndeterminateStyle { get; set; } = ProgressBar.DefaultPulseStyle;
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         return new ProgressBar
         {
diff --git a/src/Spectre.Console/Live/Progress/Columns/RemainingTimeColumn.cs b/src/Spectre.Console/Live/Progress/Columns/RemainingTimeColumn.cs
index 49aeae5..3ce467a 100644
--- a/src/Spectre.Console/Live/Progress/Columns/RemainingTimeColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/RemainingTimeColumn.cs
@@ -14,7 +14,7 @@ public sealed class RemainingTimeColumn : ProgressColumn
     public Style Style { get; set; } = new Style(foreground: Color.Blue);
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         var remaining = task.RemainingTime;
         if (remaining == null)
@@ -31,7 +31,7 @@ public sealed class RemainingTimeColumn : ProgressColumn
     }
 
     /// <inheritdoc/>
-    public override int? GetColumnWidth(RenderContext context)
+    public override int? GetColumnWidth(RenderOptions options)
     {
         return 8;
     }
diff --git a/src/Spectre.Console/Live/Progress/Columns/SpinnerColumn.cs b/src/Spectre.Console/Live/Progress/Columns/SpinnerColumn.cs
index 429d484..425de14 100644
--- a/src/Spectre.Console/Live/Progress/Columns/SpinnerColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/SpinnerColumn.cs
@@ -95,9 +95,9 @@ public sealed class SpinnerColumn : ProgressColumn
     }
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
-        var useAscii = !context.Unicode && _spinner.IsUnicode;
+        var useAscii = !options.Unicode && _spinner.IsUnicode;
         var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default;
 
         if (!task.IsStarted)
@@ -123,24 +123,24 @@ public sealed class SpinnerColumn : ProgressColumn
     }
 
     /// <inheritdoc/>
-    public override int? GetColumnWidth(RenderContext context)
+    public override int? GetColumnWidth(RenderOptions options)
     {
-        return GetMaxWidth(context);
+        return GetMaxWidth(options);
     }
 
-    private int GetMaxWidth(RenderContext context)
+    private int GetMaxWidth(RenderOptions options)
     {
         lock (_lock)
         {
             if (_maxWidth == null)
             {
-                var useAscii = !context.Unicode && _spinner.IsUnicode;
+                var useAscii = !options.Unicode && _spinner.IsUnicode;
                 var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default;
 
                 _maxWidth = Math.Max(
                     Math.Max(
-                    ((IRenderable)new Markup(PendingText ?? " ")).Measure(context, int.MaxValue).Max,
-                    ((IRenderable)new Markup(CompletedText ?? " ")).Measure(context, int.MaxValue).Max),
+                    ((IRenderable)new Markup(PendingText ?? " ")).Measure(options, int.MaxValue).Max,
+                    ((IRenderable)new Markup(CompletedText ?? " ")).Measure(options, int.MaxValue).Max),
                     spinner.Frames.Max(frame => Cell.GetCellLength(frame)));
             }
 
diff --git a/src/Spectre.Console/Live/Progress/Columns/TaskDescriptionColumn.cs b/src/Spectre.Console/Live/Progress/Columns/TaskDescriptionColumn.cs
index b6ffb0d..4e3c39f 100644
--- a/src/Spectre.Console/Live/Progress/Columns/TaskDescriptionColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/TaskDescriptionColumn.cs
@@ -14,9 +14,9 @@ public sealed class TaskDescriptionColumn : ProgressColumn
     public Justify Alignment { get; set; } = Justify.Right;
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         var text = task.Description?.RemoveNewLines()?.Trim();
-        return new Markup(text ?? string.Empty).Overflow(Overflow.Ellipsis).Alignment(Alignment);
+        return new Markup(text ?? string.Empty).Overflow(Overflow.Ellipsis).Justify(Alignment);
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs b/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
index 7e6e783..84f2743 100644
--- a/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
@@ -11,7 +11,7 @@ public sealed class TransferSpeedColumn : ProgressColumn
     public CultureInfo? Culture { get; set; }
 
     /// <inheritdoc/>
-    public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
+    public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
     {
         if (task.Speed == null)
         {
diff --git a/src/Spectre.Console/Live/Progress/ProgressColumn.cs b/src/Spectre.Console/Live/Progress/ProgressColumn.cs
index 2aa593d..c10538e 100644
--- a/src/Spectre.Console/Live/Progress/ProgressColumn.cs
+++ b/src/Spectre.Console/Live/Progress/ProgressColumn.cs
@@ -13,18 +13,18 @@ public abstract class ProgressColumn
     /// <summary>
     /// Gets a renderable representing the column.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="task">The task.</param>
     /// <param name="deltaTime">The elapsed time since last call.</param>
     /// <returns>A renderable representing the column.</returns>
-    public abstract IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime);
+    public abstract IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime);
 
     /// <summary>
     /// Gets the width of the column.
     /// </summary>
-    /// <param name="context">The context.</param>
+    /// <param name="options">The render options.</param>
     /// <returns>The width of the column, or <c>null</c> to calculate.</returns>
-    public virtual int? GetColumnWidth(RenderContext context)
+    public virtual int? GetColumnWidth(RenderOptions options)
     {
         return null;
     }
diff --git a/src/Spectre.Console/Live/Progress/ProgressRenderer.cs b/src/Spectre.Console/Live/Progress/ProgressRenderer.cs
index 598ce5a..5840b20 100644
--- a/src/Spectre.Console/Live/Progress/ProgressRenderer.cs
+++ b/src/Spectre.Console/Live/Progress/ProgressRenderer.cs
@@ -13,5 +13,5 @@ internal abstract class ProgressRenderer : IRenderHook
     }
 
     public abstract void Update(ProgressContext context);
-    public abstract IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables);
+    public abstract IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables);
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Live/Progress/Renderers/DefaultProgressRenderer.cs b/src/Spectre.Console/Live/Progress/Renderers/DefaultProgressRenderer.cs
index 9715402..8155751 100644
--- a/src/Spectre.Console/Live/Progress/Renderers/DefaultProgressRenderer.cs
+++ b/src/Spectre.Console/Live/Progress/Renderers/DefaultProgressRenderer.cs
@@ -64,7 +64,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer
                 _stopwatch.Start();
             }
 
-            var renderContext = new RenderContext(_console.Profile.Capabilities);
+            var renderContext = RenderOptions.Create(_console, _console.Profile.Capabilities);
 
             var delta = _stopwatch.Elapsed - _lastUpdate;
             _lastUpdate = _stopwatch.Elapsed;
@@ -105,7 +105,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer
         }
     }
 
-    public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+    public override IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         lock (_lock)
         {
diff --git a/src/Spectre.Console/Live/Progress/Renderers/FallbackProgressRenderer.cs b/src/Spectre.Console/Live/Progress/Renderers/FallbackProgressRenderer.cs
index 90c54b3..e40f54c 100644
--- a/src/Spectre.Console/Live/Progress/Renderers/FallbackProgressRenderer.cs
+++ b/src/Spectre.Console/Live/Progress/Renderers/FallbackProgressRenderer.cs
@@ -58,7 +58,7 @@ internal sealed class FallbackProgressRenderer : ProgressRenderer
         }
     }
 
-    public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+    public override IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         lock (_lock)
         {
diff --git a/src/Spectre.Console/Live/Progress/Renderers/FallbackStatusRenderer.cs b/src/Spectre.Console/Live/Progress/Renderers/FallbackStatusRenderer.cs
index f8f445e..69e025c 100644
--- a/src/Spectre.Console/Live/Progress/Renderers/FallbackStatusRenderer.cs
+++ b/src/Spectre.Console/Live/Progress/Renderers/FallbackStatusRenderer.cs
@@ -34,7 +34,7 @@ internal sealed class FallbackStatusRenderer : ProgressRenderer
         }
     }
 
-    public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+    public override IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         lock (_lock)
         {
diff --git a/src/Spectre.Console/Prompts/List/ListPromptRenderHook.cs b/src/Spectre.Console/Prompts/List/ListPromptRenderHook.cs
index 53fa21f..090a2f6 100644
--- a/src/Spectre.Console/Prompts/List/ListPromptRenderHook.cs
+++ b/src/Spectre.Console/Prompts/List/ListPromptRenderHook.cs
@@ -32,7 +32,7 @@ internal sealed class ListPromptRenderHook<T> : IRenderHook
         _console.Write(new ControlCode(string.Empty));
     }
 
-    public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+    public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         lock (_lock)
         {
diff --git a/src/Spectre.Console/Region.cs b/src/Spectre.Console/Region.cs
new file mode 100644
index 0000000..a5aeb05
--- /dev/null
+++ b/src/Spectre.Console/Region.cs
@@ -0,0 +1,43 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents a region.
+/// </summary>
+[DebuggerDisplay("[X={X,nq}, Y={Y,nq}, W={Width,nq}, H={Height,nq}]")]
+public struct Region
+{
+    /// <summary>
+    /// Gets the x-coordinate.
+    /// </summary>
+    public int X { get; }
+
+    /// <summary>
+    /// Gets the y-coordinate.
+    /// </summary>
+    public int Y { get; }
+
+    /// <summary>
+    /// Gets the width.
+    /// </summary>
+    public int Width { get; }
+
+    /// <summary>
+    /// Gets the height.
+    /// </summary>
+    public int Height { get; }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Region"/> struct.
+    /// </summary>
+    /// <param name="x">The x-coordinate.</param>
+    /// <param name="y">The y-coordinate.</param>
+    /// <param name="width">The width.</param>
+    /// <param name="height">The height.</param>
+    public Region(int x, int y, int width, int height)
+    {
+        X = x;
+        Y = y;
+        Width = width;
+        Height = height;
+    }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/Rendering/IRenderHook.cs b/src/Spectre.Console/Rendering/IRenderHook.cs
index 417a500..49c92d6 100644
--- a/src/Spectre.Console/Rendering/IRenderHook.cs
+++ b/src/Spectre.Console/Rendering/IRenderHook.cs
@@ -8,8 +8,8 @@ public interface IRenderHook
     /// <summary>
     /// Processes the specified renderables.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="renderables">The renderables to process.</param>
     /// <returns>The processed renderables.</returns>
-    IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables);
+    IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables);
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Rendering/JustInTimeRenderable.cs b/src/Spectre.Console/Rendering/JustInTimeRenderable.cs
index dd85e40..f30b370 100644
--- a/src/Spectre.Console/Rendering/JustInTimeRenderable.cs
+++ b/src/Spectre.Console/Rendering/JustInTimeRenderable.cs
@@ -10,13 +10,13 @@ public abstract class JustInTimeRenderable : Renderable
     private IRenderable? _rendered;
 
     /// <inheritdoc/>
-    protected sealed override Measurement Measure(RenderContext context, int maxWidth)
+    protected sealed override Measurement Measure(RenderOptions context, int maxWidth)
     {
         return GetInner().Measure(context, maxWidth);
     }
 
     /// <inheritdoc/>
-    protected sealed override IEnumerable<Segment> Render(RenderContext context, int width)
+    protected sealed override IEnumerable<Segment> Render(RenderOptions context, int width)
     {
         return GetInner().Render(context, width);
     }
diff --git a/src/Spectre.Console/Rendering/RenderContext.cs b/src/Spectre.Console/Rendering/RenderContext.cs
deleted file mode 100644
index c8d15f8..0000000
--- a/src/Spectre.Console/Rendering/RenderContext.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-namespace Spectre.Console.Rendering;
-
-/// <summary>
-/// Represents a render context.
-/// </summary>
-public sealed class RenderContext
-{
-    private readonly IReadOnlyCapabilities _capabilities;
-
-    /// <summary>
-    /// Gets the current color system.
-    /// </summary>
-    public ColorSystem ColorSystem => _capabilities.ColorSystem;
-
-    /// <summary>
-    /// Gets a value indicating whether or not VT/Ansi codes are supported.
-    /// </summary>
-    public bool Ansi => _capabilities.Ansi;
-
-    /// <summary>
-    /// Gets a value indicating whether or not unicode is supported.
-    /// </summary>
-    public bool Unicode => _capabilities.Unicode;
-
-    /// <summary>
-    /// Gets the current justification.
-    /// </summary>
-    public Justify? Justification { get; }
-
-    /// <summary>
-    /// Gets a value indicating whether the context want items to render without
-    /// line breaks and return a single line where applicable.
-    /// </summary>
-    internal bool SingleLine { get; }
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="RenderContext"/> class.
-    /// </summary>
-    /// <param name="capabilities">The capabilities.</param>
-    /// <param name="justification">The justification.</param>
-    public RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null)
-        : this(capabilities, justification, false)
-    {
-    }
-
-    private RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null, bool singleLine = false)
-    {
-        _capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
-
-        Justification = justification;
-        SingleLine = singleLine;
-    }
-
-    /// <summary>
-    /// Creates a new context with the specified justification.
-    /// </summary>
-    /// <param name="justification">The justification.</param>
-    /// <returns>A new <see cref="RenderContext"/> instance.</returns>
-    public RenderContext WithJustification(Justify? justification)
-    {
-        return new RenderContext(_capabilities, justification, SingleLine);
-    }
-
-    /// <summary>
-    /// Creates a new context that tell <see cref="IRenderable"/> instances
-    /// to not care about splitting things in new lines. Whether or not to
-    /// comply to the request is up to the item being rendered.
-    /// </summary>
-    /// <remarks>
-    /// Use with care since this has the potential to mess things up.
-    /// Only use this kind of context with items that you know about.
-    /// </remarks>
-    /// <returns>A new <see cref="RenderContext"/> instance.</returns>
-    internal RenderContext WithSingleLine()
-    {
-        return new RenderContext(_capabilities, Justification, true);
-    }
-}
\ No newline at end of file
diff --git a/src/Spectre.Console/Rendering/RenderOptions.cs b/src/Spectre.Console/Rendering/RenderOptions.cs
new file mode 100644
index 0000000..3dcf04a
--- /dev/null
+++ b/src/Spectre.Console/Rendering/RenderOptions.cs
@@ -0,0 +1,63 @@
+namespace Spectre.Console.Rendering;
+
+/// <summary>
+/// Represents render options.
+/// </summary>
+/// <param name="Capabilities">The capabilities.</param>
+/// <param name="ConsoleSize">The console size.</param>
+public record class RenderOptions(IReadOnlyCapabilities Capabilities, Size ConsoleSize)
+{
+    /// <summary>
+    /// Gets the current color system.
+    /// </summary>
+    public ColorSystem ColorSystem => Capabilities.ColorSystem;
+
+    /// <summary>
+    /// Gets a value indicating whether or not VT/Ansi codes are supported.
+    /// </summary>
+    public bool Ansi => Capabilities.Ansi;
+
+    /// <summary>
+    /// Gets a value indicating whether or not unicode is supported.
+    /// </summary>
+    public bool Unicode => Capabilities.Unicode;
+
+    /// <summary>
+    /// Gets the current justification.
+    /// </summary>
+    public Justify? Justification { get; init; }
+
+    /// <summary>
+    /// Gets the requested height.
+    /// </summary>
+    public int? Height { get; init; }
+
+    /// <summary>
+    /// Gets a value indicating whether the context want items to render without
+    /// line breaks and return a single line where applicable.
+    /// </summary>
+    internal bool SingleLine { get; init; }
+
+    /// <summary>
+    /// Creates a <see cref="RenderOptions"/> instance from a <see cref="IAnsiConsole"/>.
+    /// </summary>
+    /// <param name="console">The console.</param>
+    /// <param name="capabilities">The capabilities, or <c>null</c> to use the provided console's capabilities.</param>
+    /// <returns>A <see cref="RenderOptions"/> representing the provided <see cref="IAnsiConsole"/>.</returns>
+    public static RenderOptions Create(IAnsiConsole console, IReadOnlyCapabilities? capabilities = null)
+    {
+        if (console is null)
+        {
+            throw new ArgumentNullException(nameof(console));
+        }
+
+        return new RenderOptions(
+            capabilities ?? console.Profile.Capabilities,
+            new Size(console.Profile.Width, console.Profile.Height))
+        {
+            Justification = null,
+            Height = null,
+            SingleLine = false,
+        };
+    }
+}
diff --git a/src/Spectre.Console/Rendering/RenderPipeline.cs b/src/Spectre.Console/Rendering/RenderPipeline.cs
index 367fffd..6865c1b 100644
--- a/src/Spectre.Console/Rendering/RenderPipeline.cs
+++ b/src/Spectre.Console/Rendering/RenderPipeline.cs
@@ -44,17 +44,17 @@ public sealed class RenderPipeline
     /// <summary>
     /// Processes the specified renderables.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="renderables">The renderables to process.</param>
     /// <returns>The processed renderables.</returns>
-    public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+    public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
     {
         lock (_lock)
         {
             var current = renderables;
             for (var index = _hooks.Count - 1; index >= 0; index--)
             {
-                current = _hooks[index].Process(context, current);
+                current = _hooks[index].Process(options, current);
             }
 
             return current;
diff --git a/src/Spectre.Console/Rendering/Renderable.cs b/src/Spectre.Console/Rendering/Renderable.cs
index 2b22f5d..1234053 100644
--- a/src/Spectre.Console/Rendering/Renderable.cs
+++ b/src/Spectre.Console/Rendering/Renderable.cs
@@ -7,25 +7,25 @@ public abstract class Renderable : IRenderable
 {
     /// <inheritdoc/>
     [DebuggerStepThrough]
-    Measurement IRenderable.Measure(RenderContext context, int maxWidth)
+    Measurement IRenderable.Measure(RenderOptions options, int maxWidth)
     {
-        return Measure(context, maxWidth);
+        return Measure(options, maxWidth);
     }
 
     /// <inheritdoc/>
     [DebuggerStepThrough]
-    IEnumerable<Segment> IRenderable.Render(RenderContext context, int maxWidth)
+    IEnumerable<Segment> IRenderable.Render(RenderOptions options, int maxWidth)
     {
-        return Render(context, maxWidth);
+        return Render(options, maxWidth);
     }
 
     /// <summary>
     /// Measures the renderable object.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="maxWidth">The maximum allowed width.</param>
     /// <returns>The minimum and maximum width of the object.</returns>
-    protected virtual Measurement Measure(RenderContext context, int maxWidth)
+    protected virtual Measurement Measure(RenderOptions options, int maxWidth)
     {
         return new Measurement(maxWidth, maxWidth);
     }
@@ -33,8 +33,8 @@ public abstract class Renderable : IRenderable
     /// <summary>
     /// Renders the object.
     /// </summary>
-    /// <param name="context">The render context.</param>
+    /// <param name="options">The render options.</param>
     /// <param name="maxWidth">The maximum allowed width.</param>
     /// <returns>A collection of segments.</returns>
-    protected abstract IEnumerable<Segment> Render(RenderContext context, int maxWidth);
+    protected abstract IEnumerable<Segment> Render(RenderOptions options, int maxWidth);
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Rendering/Segment.cs b/src/Spectre.Console/Rendering/Segment.cs
index 53bd2f9..9f36a77 100644
--- a/src/Spectre.Console/Rendering/Segment.cs
+++ b/src/Spectre.Console/Rendering/Segment.cs
@@ -201,8 +201,9 @@ public class Segment
     /// </summary>
     /// <param name="segments">The segments to split into lines.</param>
     /// <param name="maxWidth">The maximum width.</param>
+    /// <param name="height">The height (if any).</param>
     /// <returns>A list of lines.</returns>
-    public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth)
+    public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth, int? height = null)
     {
         if (segments is null)
         {
@@ -294,6 +295,25 @@ public class Segment
             lines.Add(line);
         }
 
+        // Got a height specified?
+        if (height != null)
+        {
+            if (lines.Count >= height)
+            {
+                // Remove lines
+                lines.RemoveRange(height.Value, lines.Count - height.Value);
+            }
+            else
+            {
+                // Add lines
+                var missing = height - lines.Count;
+                for (var i = 0; i < missing; i++)
+                {
+                    lines.Add(new SegmentLine());
+                }
+            }
+        }
+
         return lines;
     }
 
@@ -549,6 +569,21 @@ public class Segment
         return cells;
     }
 
+    internal static List<SegmentLine> MakeWidth(int expectedWidth, List<SegmentLine> lines)
+    {
+        foreach (var line in lines)
+        {
+            var width = line.CellCount();
+            if (width < expectedWidth)
+            {
+                var diff = expectedWidth - width;
+                line.Add(new Segment(new string(' ', diff)));
+            }
+        }
+
+        return lines;
+    }
+
     internal static List<string> SplitSegment(string text, int maxCellLength)
     {
         var list = new List<string>();
diff --git a/src/Spectre.Console/Rendering/SegmentShape.cs b/src/Spectre.Console/Rendering/SegmentShape.cs
index 6b45c38..fd8c7ff 100644
--- a/src/Spectre.Console/Rendering/SegmentShape.cs
+++ b/src/Spectre.Console/Rendering/SegmentShape.cs
@@ -11,13 +11,8 @@ internal readonly struct SegmentShape
         Height = height;
     }
 
-    public static SegmentShape Calculate(RenderContext context, List<SegmentLine> lines)
+    public static SegmentShape Calculate(RenderOptions options, List<SegmentLine> lines)
     {
-        if (context is null)
-        {
-            throw new ArgumentNullException(nameof(context));
-        }
-
         if (lines is null)
         {
             throw new ArgumentNullException(nameof(lines));
@@ -36,7 +31,7 @@ internal readonly struct SegmentShape
             Math.Max(Height, other.Height));
     }
 
-    public void Apply(RenderContext context, ref List<SegmentLine> lines)
+    public void Apply(RenderOptions options, ref List<SegmentLine> lines)
     {
         foreach (var line in lines)
         {
diff --git a/src/Spectre.Console/Size.cs b/src/Spectre.Console/Size.cs
new file mode 100644
index 0000000..5a074c8
--- /dev/null
+++ b/src/Spectre.Console/Size.cs
@@ -0,0 +1,29 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents a size.
+/// </summary>
+[DebuggerDisplay("{Width,nq}x{Height,nq}")]
+public struct Size
+{
+    /// <summary>
+    /// Gets the width.
+    /// </summary>
+    public int Width { get; }
+
+    /// <summary>
+    /// Gets the height.
+    /// </summary>
+    public int Height { get; }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Size"/> struct.
+    /// </summary>
+    /// <param name="width">The width.</param>
+    /// <param name="height">The height.</param>
+    public Size(int width, int height)
+    {
+        Width = width;
+        Height = height;
+    }
+}
diff --git a/src/Spectre.Console/VerticalAlignment.cs b/src/Spectre.Console/VerticalAlignment.cs
new file mode 100644
index 0000000..dfffb77
--- /dev/null
+++ b/src/Spectre.Console/VerticalAlignment.cs
@@ -0,0 +1,22 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents vertical alignment.
+/// </summary>
+public enum VerticalAlignment
+{
+    /// <summary>
+    /// Top aligned.
+    /// </summary>
+    Top,
+
+    /// <summary>
+    /// Middle aligned.
+    /// </summary>
+    Middle,
+
+    /// <summary>
+    /// Bottom aligned.
+    /// </summary>
+    Bottom,
+}
diff --git a/src/Spectre.Console/Widgets/Align.cs b/src/Spectre.Console/Widgets/Align.cs
new file mode 100644
index 0000000..9da88b4
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Align.cs
@@ -0,0 +1,146 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents a renderable used to align content.
+/// </summary>
+public sealed class Align : Renderable
+{
+    private readonly IRenderable _renderable;
+
+    /// <summary>
+    /// Gets or sets the horizontal alignment.
+    /// </summary>
+    public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
+
+    /// <summary>
+    /// Gets or sets the vertical alignment.
+    /// </summary>
+    public VerticalAlignment? Vertical { get; set; }
+
+    /// <summary>
+    /// Gets or sets the width.
+    /// </summary>
+    public int? Width { get; set; }
+
+    /// <summary>
+    /// Gets or sets the height.
+    /// </summary>
+    public int? Height { get; set; }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Align"/> class.
+    /// </summary>
+    /// <param name="renderable">The renderable to align.</param>
+    /// <param name="horizontal">The horizontal alignment.</param>
+    /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
+    public Align(IRenderable renderable, HorizontalAlignment horizontal, VerticalAlignment? vertical = null)
+    {
+        _renderable = renderable ?? throw new ArgumentNullException(nameof(renderable));
+
+        Horizontal = horizontal;
+        Vertical = vertical;
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Align"/> class that is left aligned.
+    /// </summary>
+    /// <param name="renderable">The <see cref="IRenderable"/> to align.</param>
+    /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
+    /// <returns>A new <see cref="Align"/> object.</returns>
+    public static Align Left(IRenderable renderable, VerticalAlignment? vertical = null)
+    {
+        return new Align(renderable, HorizontalAlignment.Left, vertical);
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Align"/> class that is center aligned.
+    /// </summary>
+    /// <param name="renderable">The <see cref="IRenderable"/> to align.</param>
+    /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
+    /// <returns>A new <see cref="Align"/> object.</returns>
+    public static Align Center(IRenderable renderable, VerticalAlignment? vertical = null)
+    {
+        return new Align(renderable, HorizontalAlignment.Center, vertical);
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Align"/> class that is right aligned.
+    /// </summary>
+    /// <param name="renderable">The <see cref="IRenderable"/> to align.</param>
+    /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
+    /// <returns>A new <see cref="Align"/> object.</returns>
+    public static Align Right(IRenderable renderable, VerticalAlignment? vertical = null)
+    {
+        return new Align(renderable, HorizontalAlignment.Right, vertical);
+    }
+
+    /// <inheritdoc/>
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
+    {
+        var rendered = _renderable.Render(options with { Height = null }, maxWidth);
+        var lines = Segment.SplitLines(rendered);
+
+        var width = Math.Min(Width ?? maxWidth, maxWidth);
+        var height = Height ?? options.Height;
+
+        var blank = new SegmentLine(new[] { new Segment(new string(' ', width)) });
+
+        // Align vertically
+        if (Vertical != null && height != null)
+        {
+            switch (Vertical)
+            {
+                case VerticalAlignment.Top:
+                    {
+                        var diff = Height - lines.Count;
+                        for (var i = 0; i < diff; i++)
+                        {
+                            lines.Add(blank);
+                        }
+
+                        break;
+                    }
+
+                case VerticalAlignment.Middle:
+                    {
+                        var top = (height - lines.Count) / 2;
+                        var bottom = height - top - lines.Count;
+
+                        for (var i = 0; i < top; i++)
+                        {
+                            lines.Insert(0, blank);
+                        }
+
+                        for (var i = 0; i < bottom; i++)
+                        {
+                            lines.Add(blank);
+                        }
+
+                        break;
+                    }
+
+                case VerticalAlignment.Bottom:
+                    {
+                        var diff = Height - lines.Count;
+                        for (var i = 0; i < diff; i++)
+                        {
+                            lines.Insert(0, blank);
+                        }
+
+                        break;
+                    }
+
+                default:
+                    throw new NotSupportedException("Unknown vertical alignment");
+            }
+        }
+
+        // Align horizontally
+        foreach (var line in lines)
+        {
+            Aligner.AlignHorizontally(line, Horizontal, width);
+        }
+
+        return new SegmentLineEnumerator(lines);
+    }
+}
diff --git a/src/Spectre.Console/Widgets/Calendar.cs b/src/Spectre.Console/Widgets/Calendar.cs
index 3f4ece6..6e2bcd9 100644
--- a/src/Spectre.Console/Widgets/Calendar.cs
+++ b/src/Spectre.Console/Widgets/Calendar.cs
@@ -107,6 +107,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
     }
 
     /// <inheritdoc/>
+    [Obsolete("Use the Align widget instead. This property will be removed in a later release.")]
     public Justify? Alignment
     {
         get => _alignment;
@@ -162,6 +163,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
     {
         var culture = Culture ?? CultureInfo.InvariantCulture;
 
+#pragma warning disable CS0618 // Type or member is obsolete
         var table = new Table
         {
             Border = _border,
@@ -169,6 +171,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
             BorderStyle = _borderStyle,
             Alignment = _alignment,
         };
+#pragma warning restore CS0618 // Type or member is obsolete
 
         if (ShowHeader)
         {
diff --git a/src/Spectre.Console/Widgets/Canvas.cs b/src/Spectre.Console/Widgets/Canvas.cs
index 2718a49..cdabca0 100644
--- a/src/Spectre.Console/Widgets/Canvas.cs
+++ b/src/Spectre.Console/Widgets/Canvas.cs
@@ -70,7 +70,7 @@ public sealed class Canvas : Renderable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         if (PixelWidth < 0)
         {
@@ -88,7 +88,7 @@ public sealed class Canvas : Renderable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         if (PixelWidth < 0)
         {
diff --git a/src/Spectre.Console/Widgets/Charts/BarChart.cs b/src/Spectre.Console/Widgets/Charts/BarChart.cs
index 457116b..b6367b7 100644
--- a/src/Spectre.Console/Widgets/Charts/BarChart.cs
+++ b/src/Spectre.Console/Widgets/Charts/BarChart.cs
@@ -52,14 +52,14 @@ public sealed class BarChart : Renderable, IHasCulture
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         return new Measurement(width, width);
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         var maxValue = Math.Max(MaxValue ?? 0d, Data.Max(item => item.Value));
@@ -72,7 +72,7 @@ public sealed class BarChart : Renderable, IHasCulture
 
         if (!string.IsNullOrWhiteSpace(Label))
         {
-            grid.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment));
+            grid.AddRow(Text.Empty, new Markup(Label).Justify(LabelAlignment));
         }
 
         foreach (var item in Data)
@@ -93,6 +93,6 @@ public sealed class BarChart : Renderable, IHasCulture
                 });
         }
 
-        return ((IRenderable)grid).Render(context, width);
+        return ((IRenderable)grid).Render(options, width);
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Widgets/Charts/BreakdownBar.cs b/src/Spectre.Console/Widgets/Charts/BreakdownBar.cs
index 4db0bc4..fa1e5f9 100644
--- a/src/Spectre.Console/Widgets/Charts/BreakdownBar.cs
+++ b/src/Spectre.Console/Widgets/Charts/BreakdownBar.cs
@@ -11,13 +11,13 @@ internal sealed class BreakdownBar : Renderable
         _data = data ?? throw new ArgumentNullException(nameof(data));
     }
 
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         return new Measurement(width, width);
     }
 
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
 
diff --git a/src/Spectre.Console/Widgets/Charts/BreakdownChart.cs b/src/Spectre.Console/Widgets/Charts/BreakdownChart.cs
index b7f973c..85067c9 100644
--- a/src/Spectre.Console/Widgets/Charts/BreakdownChart.cs
+++ b/src/Spectre.Console/Widgets/Charts/BreakdownChart.cs
@@ -3,7 +3,7 @@ namespace Spectre.Console;
 /// <summary>
 /// A renderable breakdown chart.
 /// </summary>
-public sealed class BreakdownChart : Renderable, IHasCulture
+public sealed class BreakdownChart : Renderable, IHasCulture, IExpandable
 {
     /// <summary>
     /// Gets the breakdown chart data.
@@ -43,6 +43,13 @@ public sealed class BreakdownChart : Renderable, IHasCulture
     /// <remarks>Defaults to invariant culture.</remarks>
     public CultureInfo? Culture { get; set; }
 
+    /// <summary>
+    /// Gets or sets a value indicating whether or not the object should
+    /// expand to the available space. If <c>false</c>, the object's
+    /// width will be auto calculated.
+    /// </summary>
+    public bool Expand { get; set; } = true;
+
     /// <summary>
     /// Initializes a new instance of the <see cref="BreakdownChart"/> class.
     /// </summary>
@@ -53,14 +60,14 @@ public sealed class BreakdownChart : Renderable, IHasCulture
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         return new Measurement(width, width);
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
 
@@ -90,6 +97,11 @@ public sealed class BreakdownChart : Renderable, IHasCulture
             });
         }
 
-        return ((IRenderable)grid).Render(context, width);
+        if (!Expand)
+        {
+            grid.Collapse();
+        }
+
+        return ((IRenderable)grid).Render(options, width);
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Widgets/Charts/BreakdownTags.cs b/src/Spectre.Console/Widgets/Charts/BreakdownTags.cs
index 669660a..a747345 100644
--- a/src/Spectre.Console/Widgets/Charts/BreakdownTags.cs
+++ b/src/Spectre.Console/Widgets/Charts/BreakdownTags.cs
@@ -14,13 +14,13 @@ internal sealed class BreakdownTags : Renderable
         _data = data ?? throw new ArgumentNullException(nameof(data));
     }
 
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         return new Measurement(width, width);
     }
 
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var culture = Culture ?? CultureInfo.InvariantCulture;
 
@@ -29,13 +29,13 @@ internal sealed class BreakdownTags : Renderable
         {
             var panel = new Panel(GetTag(item, culture));
             panel.Inline = true;
-            panel.Padding = new Padding(0, 0);
+            panel.Padding = new Padding(0, 0, 2, 0);
             panel.NoBorder();
 
             panels.Add(panel);
         }
 
-        foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(context, maxWidth))
+        foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(options, maxWidth))
         {
             yield return segment;
         }
diff --git a/src/Spectre.Console/Widgets/Columns.cs b/src/Spectre.Console/Widgets/Columns.cs
index d6bb8de..de20d35 100644
--- a/src/Spectre.Console/Widgets/Columns.cs
+++ b/src/Spectre.Console/Widgets/Columns.cs
@@ -55,11 +55,11 @@ public sealed class Columns : Renderable, IPaddable, IExpandable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe());
 
-        var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
+        var itemWidths = _items.Select(item => item.Measure(options, maxWidth).Max).ToArray();
         var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
         if (columnCount == 0)
         {
@@ -83,11 +83,11 @@ public sealed class Columns : Renderable, IPaddable, IExpandable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe());
 
-        var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
+        var itemWidths = _items.Select(item => item.Measure(options, maxWidth).Max).ToArray();
         var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
         if (columnCount == 0)
         {
@@ -121,7 +121,7 @@ public sealed class Columns : Renderable, IPaddable, IExpandable
             table.AddRow(_items.Skip(start).Take(columnCount).ToArray());
         }
 
-        return ((IRenderable)table).Render(context, maxWidth);
+        return ((IRenderable)table).Render(options, maxWidth);
     }
 
     // Algorithm borrowed from https://github.com/willmcgugan/rich/blob/master/rich/columns.py
diff --git a/src/Spectre.Console/Widgets/ControlCode.cs b/src/Spectre.Console/Widgets/ControlCode.cs
index fa7b46e..b83d833 100644
--- a/src/Spectre.Console/Widgets/ControlCode.cs
+++ b/src/Spectre.Console/Widgets/ControlCode.cs
@@ -9,14 +9,14 @@ internal sealed class ControlCode : Renderable
         _segment = Segment.Control(control);
     }
 
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         return new Measurement(0, 0);
     }
 
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        if (context.Ansi)
+        if (options.Ansi)
         {
             yield return _segment;
         }
diff --git a/src/Spectre.Console/Widgets/Figlet/FigletText.cs b/src/Spectre.Console/Widgets/Figlet/FigletText.cs
index f17d6a5..be4ecde 100644
--- a/src/Spectre.Console/Widgets/Figlet/FigletText.cs
+++ b/src/Spectre.Console/Widgets/Figlet/FigletText.cs
@@ -3,7 +3,7 @@ namespace Spectre.Console;
 /// <summary>
 /// Represents text rendered with a FIGlet font.
 /// </summary>
-public sealed class FigletText : Renderable, IAlignable
+public sealed class FigletText : Renderable, IHasJustification
 {
     private readonly FigletFont _font;
     private readonly string _text;
@@ -14,7 +14,7 @@ public sealed class FigletText : Renderable, IAlignable
     public Color? Color { get; set; }
 
     /// <inheritdoc/>
-    public Justify? Alignment { get; set; }
+    public Justify? Justification { get; set; }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="FigletText"/> class.
@@ -37,10 +37,10 @@ public sealed class FigletText : Renderable, IAlignable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var style = new Style(Color ?? Console.Color.Default);
-        var alignment = Alignment ?? Justify.Left;
+        var alignment = Justification ?? Console.Justify.Left;
 
         foreach (var row in GetRows(maxWidth))
         {
@@ -49,7 +49,7 @@ public sealed class FigletText : Renderable, IAlignable
                 var line = new Segment(string.Concat(row.Select(x => x.Lines[index])), style);
 
                 var lineWidth = line.CellCount();
-                if (alignment == Justify.Left)
+                if (alignment == Console.Justify.Left)
                 {
                     yield return line;
 
@@ -58,7 +58,7 @@ public sealed class FigletText : Renderable, IAlignable
                         yield return Segment.Padding(maxWidth - lineWidth);
                     }
                 }
-                else if (alignment == Justify.Center)
+                else if (alignment == Console.Justify.Center)
                 {
                     var left = (maxWidth - lineWidth) / 2;
                     var right = left + ((maxWidth - lineWidth) % 2);
@@ -67,7 +67,7 @@ public sealed class FigletText : Renderable, IAlignable
                     yield return line;
                     yield return Segment.Padding(right);
                 }
-                else if (alignment == Justify.Right)
+                else if (alignment == Console.Justify.Right)
                 {
                     if (lineWidth < maxWidth)
                     {
diff --git a/src/Spectre.Console/Widgets/Grid.cs b/src/Spectre.Console/Widgets/Grid.cs
index 448ef1b..7a14798 100644
--- a/src/Spectre.Console/Widgets/Grid.cs
+++ b/src/Spectre.Console/Widgets/Grid.cs
@@ -30,6 +30,7 @@ public sealed class Grid : JustInTimeRenderable, IExpandable, IAlignable
     }
 
     /// <inheritdoc/>
+    [Obsolete("Use the Align widget instead. This property will be removed in a later release.")]
     public Justify? Alignment
     {
         get => _alignment;
diff --git a/src/Spectre.Console/Widgets/Layout/Layout.cs b/src/Spectre.Console/Widgets/Layout/Layout.cs
new file mode 100644
index 0000000..f3bc0c4
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Layout/Layout.cs
@@ -0,0 +1,311 @@
+namespace Spectre.Console;
+
+/// <summary>
+/// Represents a renderable to divide a fixed height into rows or columns.
+/// </summary>
+public sealed class Layout : Renderable, IRatioResolvable, IHasVisibility
+{
+    private LayoutSplitter _splitter;
+    private Layout[] _children;
+    private IRenderable _renderable;
+    private int _ratio;
+    private int _minimumSize;
+    private int? _size;
+
+    /// <summary>
+    /// Gets or sets the name.
+    /// </summary>
+    public string? Name { get; set; }
+
+    /// <summary>
+    /// Gets or sets the ratio.
+    /// </summary>
+    /// <remarks>
+    /// Defaults to <c>1</c>.
+    /// Must be greater than <c>0</c>.
+    /// </remarks>
+    public int Ratio
+    {
+        get => _ratio;
+        set
+        {
+            if (value < 1)
+            {
+                throw new InvalidOperationException("Ratio must be equal to or greater than 1");
+            }
+
+            _ratio = value;
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the minimum width.
+    /// </summary>
+    /// <remarks>
+    /// Defaults to <c>1</c>.
+    /// Must be greater than <c>0</c>.
+    /// </remarks>
+    public int MinimumSize
+    {
+        get => _minimumSize;
+        set
+        {
+            if (value < 1)
+            {
+                throw new InvalidOperationException("Minimum size must be equal to or greater than 1");
+            }
+
+            _minimumSize = value;
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the width.
+    /// </summary>
+    /// <remarks>
+    /// Defaults to <c>null</c>.
+    /// Must be greater than <c>0</c>.
+    /// </remarks>
+    public int? Size
+    {
+        get => _size;
+        set
+        {
+            if (value < 1)
+            {
+                throw new InvalidOperationException("Size must be equal to or greater than 1");
+            }
+
+            _size = value;
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether or not the layout should
+    /// be visible or not.
+    /// </summary>
+    /// <remarks>Defaults to <c>true</c>.</remarks>
+    public bool IsVisible { get; set; } = true;
+
+    /// <summary>
+    /// Gets the splitter used for this layout.
+    /// </summary>
+    internal LayoutSplitter Splitter => _splitter;
+
+    /// <summary>
+    /// Gets the <see cref="IRenderable"/> associated with this layout.
+    /// </summary>
+    internal IRenderable Renderable => _renderable;
+
+    /// <summary>
+    /// Gets a child layout by it's name.
+    /// </summary>
+    /// <param name="name">The layout name.</param>
+    /// <returns>The specified child <see cref="Layout"/>.</returns>
+    public Layout this[string name]
+    {
+        get => GetLayout(name);
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Layout"/> class.
+    /// </summary>
+    /// <param name="name">The layout name.</param>
+    public Layout(string name)
+        : this(name, null)
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Layout"/> class.
+    /// </summary>
+    /// <param name="renderable">The renderable.</param>
+    public Layout(IRenderable renderable)
+        : this(null, renderable)
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="Layout"/> class.
+    /// </summary>
+    /// <param name="name">The layout name.</param>
+    /// <param name="renderable">The renderable.</param>
+    public Layout(string? name = null, IRenderable? renderable = null)
+    {
+        _splitter = LayoutSplitter.Null;
+        _children = Array.Empty<Layout>();
+        _renderable = renderable ?? new LayoutPlaceholder(this);
+        _ratio = 1;
+        _size = null;
+
+        Name = name;
+    }
+
+    /// <summary>
+    /// Gets a child layout by it's name.
+    /// </summary>
+    /// <param name="name">The layout name.</param>
+    /// <returns>The specified child <see cref="Layout"/>.</returns>
+    public Layout GetLayout(string name)
+    {
+        if (string.IsNullOrEmpty(name))
+        {
+            throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name));
+        }
+
+        var stack = new Stack<Layout>();
+        stack.Push(this);
+
+        while (stack.Count > 0)
+        {
+            var current = stack.Pop();
+            if (name.Equals(current.Name, StringComparison.OrdinalIgnoreCase))
+            {
+                return current;
+            }
+
+            foreach (var layout in current.GetChildren())
+            {
+                stack.Push(layout);
+            }
+        }
+
+        throw new InvalidOperationException($"Could not find layout '{name}'");
+    }
+
+    /// <summary>
+    /// Splits the layout into rows.
+    /// </summary>
+    /// <param name="children">The layout to split into rows.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public Layout SplitRows(params Layout[] children)
+    {
+        Split(LayoutSplitter.Row, children);
+        return this;
+    }
+
+    /// <summary>
+    /// Splits the layout into columns.
+    /// </summary>
+    /// <param name="children">The layout to split into columns.</param>
+    /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public Layout SplitColumns(params Layout[] children)
+    {
+        Split(LayoutSplitter.Column, children);
+        return this;
+    }
+
+    /// <summary>
+    /// Updates the containing <see cref="IRenderable"/>.
+    /// </summary>
+    /// <param name="renderable">The renderable to use for this layout.</param>
+    /// /// <returns>The same instance so that multiple calls can be chained.</returns>
+    public Layout Update(IRenderable renderable)
+    {
+        _renderable = renderable ?? new LayoutPlaceholder(this);
+        return this;
+    }
+
+    /// <inheritdoc/>
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
+    {
+        var height = options.Height ?? options.ConsoleSize.Height;
+        var map = MakeRenderMap(options, maxWidth);
+
+        var layoutLines = new List<SegmentLine>();
+        layoutLines.AddRange(Enumerable.Range(0, height).Select(x => new SegmentLine()));
+
+        foreach (var (region, lines) in map.Values.Select(x => (x.Region, x.Render)))
+        {
+            foreach (var line in layoutLines
+                .Skip(region.Y)
+                .Take(region.Y + region.Height)
+                .Enumerate().Select(x => (Index: x.Index + region.Y, Line: x.Item))
+                .Zip(lines, (first, second) => (first.Index, Line: second)))
+            {
+                layoutLines[line.Index].AddRange(line.Line);
+            }
+        }
+
+        // Return all the segments in all the lines
+        foreach (var (_, _, last, line) in layoutLines.Enumerate())
+        {
+            foreach (var segment in line)
+            {
+                yield return segment;
+            }
+
+            if (!last)
+            {
+                yield return Segment.LineBreak;
+            }
+        }
+    }
+
+    private IEnumerable<Layout> GetChildren(bool visibleOnly = false)
+    {
+        return visibleOnly ? _children.Where(c => c.IsVisible) : _children;
+    }
+
+    private bool HasChildren(bool visibleOnly = false)
+    {
+        return visibleOnly ? _children.Any(c => c.IsVisible) : _children.Any();
+    }
+
+    private void Split(LayoutSplitter splitter, Layout[] layouts)
+    {
+        if (_children.Length > 0)
+        {
+            throw new InvalidOperationException("Cannot split the same layout twice");
+        }
+
+        _splitter = splitter ?? throw new ArgumentNullException(nameof(splitter));
+        _children = layouts ?? throw new ArgumentNullException(nameof(layouts));
+    }
+
+    private Dictionary<Layout, LayoutRender> MakeRenderMap(RenderOptions options, int maxWidth)
+    {
+        var result = new Dictionary<Layout, LayoutRender>();
+
+        var renderWidth = maxWidth;
+        var renderHeight = options.Height ?? options.ConsoleSize.Height;
+        var regionMap = MakeRegionMap(maxWidth, renderHeight);
+
+        foreach (var (layout, region) in regionMap.Where(x => !x.Layout.HasChildren(visibleOnly: true)))
+        {
+            var segments = layout.Renderable.Render(options with { Height = region.Height }, region.Width);
+
+            var lines = Segment.SplitLines(segments, region.Width, region.Height);
+            lines = Segment.MakeWidth(region.Width, lines);
+
+            result[layout] = new LayoutRender(region, lines);
+        }
+
+        return result;
+    }
+
+    private IEnumerable<(Layout Layout, Region Region)> MakeRegionMap(int width, int height)
+    {
+        var stack = new Stack<(Layout Layout, Region Region)>();
+        stack.Push((this, new Region(0, 0, width, height)));
+
+        var result = new List<(Layout Layout, Region Region)>();
+
+        while (stack.Count > 0)
+        {
+            var current = stack.Pop();
+            result.Add(current);
+
+            if (current.Layout.HasChildren(visibleOnly: true))
+            {
+                foreach (var childAndRegion in current.Layout.Splitter
+                    .Divide(current.Region, current.Layout.GetChildren(visibleOnly: true)))
+                {
+                    stack.Push(childAndRegion);
+                }
+            }
+        }
+
+        return result.ReverseEnumerable();
+    }
+}
diff --git a/src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs b/src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
new file mode 100644
index 0000000..46b959a
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
@@ -0,0 +1,31 @@
+namespace Spectre.Console;
+
+internal sealed class LayoutPlaceholder : Renderable
+{
+    public Layout Layout { get; }
+
+    public LayoutPlaceholder(Layout layout)
+    {
+        Layout = layout ?? throw new ArgumentNullException(nameof(layout));
+    }
+
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
+    {
+        var width = maxWidth;
+        var height = options.Height ?? options.ConsoleSize.Height;
+        var title = Layout.Name != null
+            ? $"{Layout.Name} ({width} x {height})"
+            : $"{width} x {height}";
+
+        var panel = new Panel(
+            Align.Center(new Text("Placeholder"), VerticalAlignment.Middle))
+        {
+            Width = maxWidth,
+            Height = options.Height ?? options.ConsoleSize.Height,
+            Header = new PanelHeader(title),
+            Border = BoxBorder.Rounded,
+        };
+
+        return ((IRenderable)panel).Render(options, maxWidth);
+    }
+}
diff --git a/src/Spectre.Console/Widgets/Layout/LayoutRender.cs b/src/Spectre.Console/Widgets/Layout/LayoutRender.cs
new file mode 100644
index 0000000..a608be7
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Layout/LayoutRender.cs
@@ -0,0 +1,14 @@
+namespace Spectre.Console;
+
+[DebuggerDisplay("{Region,nq}")]
+internal sealed class LayoutRender
+{
+    public Region Region { get; }
+    public List<SegmentLine> Render { get; }
+
+    public LayoutRender(Region region, List<SegmentLine> render)
+    {
+        Region = region;
+        Render = render ?? throw new ArgumentNullException(nameof(render));
+    }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs b/src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
new file mode 100644
index 0000000..6be913c
--- /dev/null
+++ b/src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
@@ -0,0 +1,48 @@
+namespace Spectre.Console;
+
+internal abstract class LayoutSplitter
+{
+    public static LayoutSplitter Column { get; } = new ColumnSplitter();
+    public static LayoutSplitter Row { get; } = new RowSplitter();
+    public static LayoutSplitter Null { get; } = new NullSplitter();
+
+    public abstract IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> layouts);
+
+    private sealed class NullSplitter : LayoutSplitter
+    {
+        public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> layouts)
+        {
+            yield break;
+        }
+    }
+
+    private sealed class ColumnSplitter : LayoutSplitter
+    {
+        public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> children)
+        {
+            var widths = Ratio.Resolve(region.Width, children);
+            var offset = 0;
+
+            foreach (var (child, childWidth) in children.Zip(widths, (child, width) => (child, width)))
+            {
+                yield return (child, new Region(region.X + offset, region.Y, childWidth, region.Height));
+                offset += childWidth;
+            }
+        }
+    }
+
+    private sealed class RowSplitter : LayoutSplitter
+    {
+        public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> children)
+        {
+            var heights = Ratio.Resolve(region.Height, children);
+            var offset = 0;
+
+            foreach (var (child, childHeight) in children.Zip(heights, (child, height) => (child, height)))
+            {
+                yield return (child, new Region(region.X, region.Y + offset, region.Width, childHeight));
+                offset += childHeight;
+            }
+        }
+    }
+}
diff --git a/src/Spectre.Console/Widgets/Markup.cs b/src/Spectre.Console/Widgets/Markup.cs
index 7b23376..5e43809 100644
--- a/src/Spectre.Console/Widgets/Markup.cs
+++ b/src/Spectre.Console/Widgets/Markup.cs
@@ -4,15 +4,15 @@ namespace Spectre.Console;
 /// A renderable piece of markup text.
 /// </summary>
 [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
-public sealed class Markup : Renderable, IAlignable, IOverflowable
+public sealed class Markup : Renderable, IHasJustification, IOverflowable
 {
     private readonly Paragraph _paragraph;
 
     /// <inheritdoc/>
-    public Justify? Alignment
+    public Justify? Justification
     {
-        get => _paragraph.Alignment;
-        set => _paragraph.Alignment = value;
+        get => _paragraph.Justification;
+        set => _paragraph.Justification = value;
     }
 
     /// <inheritdoc/>
@@ -43,15 +43,15 @@ public sealed class Markup : Renderable, IAlignable, IOverflowable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
-        return ((IRenderable)_paragraph).Measure(context, maxWidth);
+        return ((IRenderable)_paragraph).Measure(options, maxWidth);
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        return ((IRenderable)_paragraph).Render(context, maxWidth);
+        return ((IRenderable)_paragraph).Render(options, maxWidth);
     }
 
     /// <summary>
diff --git a/src/Spectre.Console/Widgets/Padder.cs b/src/Spectre.Console/Widgets/Padder.cs
index 9f2d376..eeecdf1 100644
--- a/src/Spectre.Console/Widgets/Padder.cs
+++ b/src/Spectre.Console/Widgets/Padder.cs
@@ -29,10 +29,10 @@ public sealed class Padder : Renderable, IPaddable, IExpandable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var paddingWidth = Padding?.GetWidth() ?? 0;
-        var measurement = _child.Measure(context, maxWidth - paddingWidth);
+        var measurement = _child.Measure(options, maxWidth - paddingWidth);
 
         return new Measurement(
             measurement.Min + paddingWidth,
@@ -40,14 +40,14 @@ public sealed class Padder : Renderable, IPaddable, IExpandable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var paddingWidth = Padding?.GetWidth() ?? 0;
         var childWidth = maxWidth - paddingWidth;
 
         if (!Expand)
         {
-            var measurement = _child.Measure(context, maxWidth - paddingWidth);
+            var measurement = _child.Measure(options, maxWidth - paddingWidth);
             childWidth = measurement.Max;
         }
 
@@ -66,7 +66,7 @@ public sealed class Padder : Renderable, IPaddable, IExpandable
             result.Add(Segment.LineBreak);
         }
 
-        var child = _child.Render(context, maxWidth - paddingWidth);
+        var child = _child.Render(options, maxWidth - paddingWidth);
         foreach (var line in Segment.SplitLines(child))
         {
             // Left padding
diff --git a/src/Spectre.Console/Widgets/Panel.cs b/src/Spectre.Console/Widgets/Panel.cs
index 0c263cb..05d41d4 100644
--- a/src/Spectre.Console/Widgets/Panel.cs
+++ b/src/Spectre.Console/Widgets/Panel.cs
@@ -35,6 +35,16 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
     /// </summary>
     public PanelHeader? Header { get; set; }
 
+    /// <summary>
+    /// Gets or sets the width of the panel.
+    /// </summary>
+    public int? Width { get; set; }
+
+    /// <summary>
+    /// Gets or sets the height of the panel.
+    /// </summary>
+    public int? Height { get; set; }
+
     /// <summary>
     /// Gets or sets a value indicating whether or not the panel is inlined.
     /// </summary>
@@ -59,54 +69,73 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var child = new Padder(_child, Padding);
-        var childWidth = ((IRenderable)child).Measure(context, maxWidth);
+        return Measure(options, maxWidth, child);
+    }
+
+    private Measurement Measure(RenderOptions options, int maxWidth, IRenderable child)
+    {
+        var edgeWidth = (options.GetSafeBorder(this) is not NoBoxBorder) ? EdgeWidth : 0;
+        var childWidth = child.Measure(options, maxWidth - edgeWidth);
+
+        if (Width != null)
+        {
+            var width = Width.Value - edgeWidth;
+            if (width > childWidth.Max)
+            {
+                childWidth = new Measurement(
+                    childWidth.Min,
+                    width);
+            }
+        }
+
         return new Measurement(
-            childWidth.Min + EdgeWidth,
-            childWidth.Max + EdgeWidth);
+            childWidth.Min + edgeWidth,
+            childWidth.Max + edgeWidth);
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        var edgeWidth = EdgeWidth;
-
-        var border = BoxExtensions.GetSafeBorder(Border, !context.Unicode && UseSafeBorder);
+        var border = options.GetSafeBorder(this);
         var borderStyle = BorderStyle ?? Style.Plain;
 
-        var showBorder = true;
-        if (border is NoBoxBorder)
-        {
-            showBorder = false;
-            edgeWidth = 0;
-        }
+        var showBorder = border is not NoBoxBorder;
+        var edgeWidth = showBorder ? EdgeWidth : 0;
 
         var child = new Padder(_child, Padding);
-        var childWidth = maxWidth - edgeWidth;
+        var width = Measure(options, maxWidth, child);
+
+        var panelWidth = Math.Min(!Expand ? width.Max : maxWidth, maxWidth);
+        var innerWidth = panelWidth - edgeWidth;
+
+        var height = Height != null
+            ? Height - 2
+            : options.Height != null
+                ? options.Height - 2
+                : null;
 
         if (!Expand)
         {
-            var measurement = ((IRenderable)child).Measure(context, maxWidth - edgeWidth);
-            childWidth = measurement.Max;
+            // Set the height to the explicit height (or null)
+            // if the panel isn't expandable.
+            height = Height != null ? Height - 2 : null;
         }
 
-        var panelWidth = childWidth + edgeWidth;
-        panelWidth = Math.Min(panelWidth, maxWidth);
-        childWidth = panelWidth - edgeWidth;
-
+        // Start building the panel
         var result = new List<Segment>();
 
+        // Panel top
         if (showBorder)
         {
-            // Panel top
-            AddTopBorder(result, context, border, borderStyle, panelWidth);
+            AddTopBorder(result, options, border, borderStyle, panelWidth);
         }
 
         // Split the child segments into lines.
-        var childSegments = ((IRenderable)child).Render(context, childWidth);
-        foreach (var (_, _, last, line) in Segment.SplitLines(childSegments, childWidth).Enumerate())
+        var childSegments = ((IRenderable)child).Render(options with { Height = height }, innerWidth);
+        foreach (var (_, _, last, line) in Segment.SplitLines(childSegments, innerWidth, height).Enumerate())
         {
             if (line.Count == 1 && line[0].IsWhiteSpace)
             {
@@ -125,9 +154,9 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
 
             // Do we need to pad the panel?
             var length = line.Sum(segment => segment.CellCount());
-            if (length < childWidth)
+            if (length < innerWidth)
             {
-                var diff = childWidth - length;
+                var diff = innerWidth - length;
                 content.Add(Segment.Padding(diff));
             }
 
@@ -170,7 +199,7 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
     }
 
     private void AddTopBorder(
-        List<Segment> result, RenderContext context, BoxBorder border,
+        List<Segment> result, RenderOptions options, BoxBorder border,
         Style borderStyle, int panelWidth)
     {
         var rule = new Rule
@@ -180,14 +209,14 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
             TitlePadding = 1,
             TitleSpacing = 0,
             Title = Header?.Text,
-            Alignment = Header?.Alignment ?? Justify.Left,
+            Justification = Header?.Justification ?? Justify.Left,
         };
 
         // Top left border
         result.Add(new Segment(border.GetPart(BoxBorderPart.TopLeft), borderStyle));
 
         // Top border (and header text if specified)
-        result.AddRange(((IRenderable)rule).Render(context, panelWidth - 2).Where(x => !x.IsLineBreak));
+        result.AddRange(((IRenderable)rule).Render(options, panelWidth - 2).Where(x => !x.IsLineBreak));
 
         // Top right border
         result.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle));
diff --git a/src/Spectre.Console/Widgets/PanelHeader.cs b/src/Spectre.Console/Widgets/PanelHeader.cs
index 951dcbd..97cdd33 100644
--- a/src/Spectre.Console/Widgets/PanelHeader.cs
+++ b/src/Spectre.Console/Widgets/PanelHeader.cs
@@ -3,7 +3,7 @@ namespace Spectre.Console;
 /// <summary>
 /// Represents a panel header.
 /// </summary>
-public sealed class PanelHeader : IAlignable
+public sealed class PanelHeader : IHasJustification
 {
     /// <summary>
     /// Gets the panel header text.
@@ -13,7 +13,7 @@ public sealed class PanelHeader : IAlignable
     /// <summary>
     /// Gets or sets the panel header alignment.
     /// </summary>
-    public Justify? Alignment { get; set; }
+    public Justify? Justification { get; set; }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="PanelHeader"/> class.
@@ -23,7 +23,7 @@ public sealed class PanelHeader : IAlignable
     public PanelHeader(string text, Justify? alignment = null)
     {
         Text = text ?? throw new ArgumentNullException(nameof(text));
-        Alignment = alignment;
+        Justification = alignment;
     }
 
     /// <summary>
@@ -57,7 +57,7 @@ public sealed class PanelHeader : IAlignable
     /// <returns>The same instance so that multiple calls can be chained.</returns>
     public PanelHeader SetAlignment(Justify alignment)
     {
-        Alignment = alignment;
+        Justification = alignment;
         return this;
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Widgets/Paragraph.cs b/src/Spectre.Console/Widgets/Paragraph.cs
index 5ac2f8e..96012d4 100644
--- a/src/Spectre.Console/Widgets/Paragraph.cs
+++ b/src/Spectre.Console/Widgets/Paragraph.cs
@@ -5,14 +5,14 @@ namespace Spectre.Console;
 /// of the paragraph can have individual styling.
 /// </summary>
 [DebuggerDisplay("{_text,nq}")]
-public sealed class Paragraph : Renderable, IAlignable, IOverflowable
+public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
 {
     private readonly List<SegmentLine> _lines;
 
     /// <summary>
     /// Gets or sets the alignment of the whole paragraph.
     /// </summary>
-    public Justify? Alignment { get; set; }
+    public Justify? Justification { get; set; }
 
     /// <summary>
     /// Gets or sets the text overflow strategy.
@@ -115,7 +115,7 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         if (_lines.Count == 0)
         {
@@ -129,11 +129,11 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        if (context is null)
+        if (options is null)
         {
-            throw new ArgumentNullException(nameof(context));
+            throw new ArgumentNullException(nameof(options));
         }
 
         if (_lines.Count == 0)
@@ -141,13 +141,13 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
             return Array.Empty<Segment>();
         }
 
-        var lines = context.SingleLine
+        var lines = options.SingleLine
             ? new List<SegmentLine>(_lines)
             : SplitLines(maxWidth);
 
         // Justify lines
-        var justification = context.Justification ?? Alignment ?? Justify.Left;
-        if (justification != Justify.Left)
+        var justification = options.Justification ?? Justification ?? Console.Justify.Left;
+        if (justification != Console.Justify.Left)
         {
             foreach (var line in lines)
             {
@@ -155,7 +155,7 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
             }
         }
 
-        if (context.SingleLine)
+        if (options.SingleLine)
         {
             // Return the first line
             return lines[0].Where(segment => !segment.IsLineBreak);
diff --git a/src/Spectre.Console/Widgets/ProgressBar.cs b/src/Spectre.Console/Widgets/ProgressBar.cs
index 67ef339..6b59a42 100644
--- a/src/Spectre.Console/Widgets/ProgressBar.cs
+++ b/src/Spectre.Console/Widgets/ProgressBar.cs
@@ -23,13 +23,13 @@ internal sealed class ProgressBar : Renderable, IHasCulture
 
     internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23);
 
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         return new Measurement(4, width);
     }
 
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var width = Math.Min(Width ?? maxWidth, maxWidth);
         var completedBarCount = Math.Min(MaxValue, Math.Max(0, Value));
@@ -37,7 +37,7 @@ internal sealed class ProgressBar : Renderable, IHasCulture
 
         if (IsIndeterminate && !isCompleted)
         {
-            foreach (var segment in RenderIndeterminate(context, width))
+            foreach (var segment in RenderIndeterminate(options, width))
             {
                 yield return segment;
             }
@@ -45,7 +45,7 @@ internal sealed class ProgressBar : Renderable, IHasCulture
             yield break;
         }
 
-        var bar = !context.Unicode ? AsciiBar : UnicodeBar;
+        var bar = !options.Unicode ? AsciiBar : UnicodeBar;
         var style = isCompleted ? FinishedStyle : CompletedStyle;
         var barCount = Math.Max(0, (int)(width * (completedBarCount / MaxValue)));
 
@@ -84,29 +84,29 @@ internal sealed class ProgressBar : Renderable, IHasCulture
                 }
             }
 
-            var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy;
+            var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy;
             var remainingToken = ShowRemaining && !legacy ? bar : ' ';
             yield return new Segment(new string(remainingToken, diff), RemainingStyle);
         }
     }
 
-    private IEnumerable<Segment> RenderIndeterminate(RenderContext context, int width)
+    private IEnumerable<Segment> RenderIndeterminate(RenderOptions options, int width)
     {
-        var bar = context.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString();
+        var bar = options.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString();
         var style = IndeterminateStyle ?? DefaultPulseStyle;
 
         IEnumerable<Segment> GetPulseSegments()
         {
             // For 1-bit and 3-bit colors, fall back to
             // a simpler versions with only two colors.
-            if (context.ColorSystem == ColorSystem.NoColors ||
-                context.ColorSystem == ColorSystem.Legacy)
+            if (options.ColorSystem == ColorSystem.NoColors ||
+                options.ColorSystem == ColorSystem.Legacy)
             {
                 // First half of the pulse
                 var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2);
 
                 // Second half of the pulse
-                var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy;
+                var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy;
                 var bar2 = legacy ? " " : bar;
                 segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2)));
 
diff --git a/src/Spectre.Console/Widgets/Rows.cs b/src/Spectre.Console/Widgets/Rows.cs
index 32f88cf..9539440 100644
--- a/src/Spectre.Console/Widgets/Rows.cs
+++ b/src/Spectre.Console/Widgets/Rows.cs
@@ -29,7 +29,7 @@ public sealed class Rows : Renderable, IExpandable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
         if (Expand)
         {
@@ -37,7 +37,7 @@ public sealed class Rows : Renderable, IExpandable
         }
         else
         {
-            var measurements = _children.Select(c => c.Measure(context, maxWidth));
+            var measurements = _children.Select(c => c.Measure(options, maxWidth));
             return new Measurement(
                 measurements.Min(c => c.Min),
                 measurements.Min(c => c.Max));
@@ -45,13 +45,13 @@ public sealed class Rows : Renderable, IExpandable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var result = new List<Segment>();
 
         foreach (var child in _children)
         {
-            var segments = child.Render(context, maxWidth);
+            var segments = child.Render(options, maxWidth);
             foreach (var (_, _, last, segment) in segments.Enumerate())
             {
                 result.Add(segment);
diff --git a/src/Spectre.Console/Widgets/Rule.cs b/src/Spectre.Console/Widgets/Rule.cs
index 210318c..0bcef99 100644
--- a/src/Spectre.Console/Widgets/Rule.cs
+++ b/src/Spectre.Console/Widgets/Rule.cs
@@ -3,7 +3,7 @@ namespace Spectre.Console;
 /// <summary>
 /// A renderable horizontal rule.
 /// </summary>
-public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
+public sealed class Rule : Renderable, IHasJustification, IHasBoxBorder
 {
     /// <summary>
     /// Gets or sets the rule title markup text.
@@ -16,9 +16,9 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
     public Style? Style { get; set; }
 
     /// <summary>
-    /// Gets or sets the rule's title alignment.
+    /// Gets or sets the rule's title justification.
     /// </summary>
-    public Justify? Alignment { get; set; }
+    public Justify? Justification { get; set; }
 
     /// <inheritdoc/>
     public BoxBorder Border { get; set; } = BoxBorder.Square;
@@ -43,17 +43,17 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var extraLength = (2 * TitlePadding) + (2 * TitleSpacing);
 
         if (Title == null || maxWidth <= extraLength)
         {
-            return GetLineWithoutTitle(context, maxWidth);
+            return GetLineWithoutTitle(options, maxWidth);
         }
 
         // Get the title and make sure it fits.
-        var title = GetTitleSegments(context, Title, maxWidth - extraLength);
+        var title = GetTitleSegments(options, Title, maxWidth - extraLength);
         if (Segment.CellCount(title) > maxWidth - extraLength)
         {
             // Truncate the title
@@ -61,11 +61,11 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
             if (!title.Any())
             {
                 // We couldn't fit the title at all.
-                return GetLineWithoutTitle(context, maxWidth);
+                return GetLineWithoutTitle(options, maxWidth);
             }
         }
 
-        var (left, right) = GetLineSegments(context, maxWidth, title);
+        var (left, right) = GetLineSegments(options, maxWidth, title);
 
         var segments = new List<Segment>();
         segments.Add(left);
@@ -76,9 +76,9 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
         return segments;
     }
 
-    private IEnumerable<Segment> GetLineWithoutTitle(RenderContext context, int maxWidth)
+    private IEnumerable<Segment> GetLineWithoutTitle(RenderOptions options, int maxWidth)
     {
-        var border = Border.GetSafeBorder(safe: !context.Unicode);
+        var border = Border.GetSafeBorder(safe: !options.Unicode);
         var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth);
 
         return new[]
@@ -88,21 +88,21 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
         };
     }
 
-    private IEnumerable<Segment> GetTitleSegments(RenderContext context, string title, int width)
+    private IEnumerable<Segment> GetTitleSegments(RenderOptions options, string title, int width)
     {
         title = title.NormalizeNewLines().ReplaceExact("\n", " ").Trim();
         var markup = new Markup(title, Style);
-        return ((IRenderable)markup).Render(context.WithSingleLine(), width);
+        return ((IRenderable)markup).Render(options with { SingleLine = true }, width);
     }
 
-    private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int width, IEnumerable<Segment> title)
+    private (Segment Left, Segment Right) GetLineSegments(RenderOptions options, int width, IEnumerable<Segment> title)
     {
         var titleLength = Segment.CellCount(title);
 
-        var border = Border.GetSafeBorder(safe: !context.Unicode);
+        var border = Border.GetSafeBorder(safe: !options.Unicode);
         var borderPart = border.GetPart(BoxBorderPart.Top);
 
-        var alignment = Alignment ?? Justify.Center;
+        var alignment = Justification ?? Justify.Center;
         if (alignment == Justify.Left)
         {
             var left = new Segment(borderPart.Repeat(TitlePadding) + new string(' ', TitleSpacing), Style ?? Style.Plain);
diff --git a/src/Spectre.Console/Widgets/Table/Table.cs b/src/Spectre.Console/Widgets/Table/Table.cs
index a22dd63..c0d83e1 100644
--- a/src/Spectre.Console/Widgets/Table/Table.cs
+++ b/src/Spectre.Console/Widgets/Table/Table.cs
@@ -59,6 +59,7 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
     public TableTitle? Caption { get; set; }
 
     /// <inheritdoc/>
+    [Obsolete("Use the Align widget instead. This property will be removed in a later release.")]
     public Justify? Alignment { get; set; }
 
     // Whether this is a grid or not.
@@ -100,14 +101,14 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
     }
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
-        if (context is null)
+        if (options is null)
         {
-            throw new ArgumentNullException(nameof(context));
+            throw new ArgumentNullException(nameof(options));
         }
 
-        var measurer = new TableMeasurer(this, context);
+        var measurer = new TableMeasurer(this, options);
 
         // Calculate the total cell width
         var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth);
@@ -120,14 +121,14 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        if (context is null)
+        if (options is null)
         {
-            throw new ArgumentNullException(nameof(context));
+            throw new ArgumentNullException(nameof(options));
         }
 
-        var measurer = new TableMeasurer(this, context);
+        var measurer = new TableMeasurer(this, options);
 
         // Calculate the column and table width
         var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth);
@@ -139,7 +140,7 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
 
         // Render the table
         return TableRenderer.Render(
-            new TableRendererContext(this, context, rows, tableWidth, maxWidth),
+            new TableRendererContext(this, options, rows, tableWidth, maxWidth),
             columnWidths);
     }
 
diff --git a/src/Spectre.Console/Widgets/Table/TableAccessor.cs b/src/Spectre.Console/Widgets/Table/TableAccessor.cs
index 2979a0d..3dda3ee 100644
--- a/src/Spectre.Console/Widgets/Table/TableAccessor.cs
+++ b/src/Spectre.Console/Widgets/Table/TableAccessor.cs
@@ -4,12 +4,12 @@ internal abstract class TableAccessor
 {
     private readonly Table _table;
 
-    public RenderContext Options { get; }
+    public RenderOptions Options { get; }
     public IReadOnlyList<TableColumn> Columns => _table.Columns;
     public virtual IReadOnlyList<TableRow> Rows => _table.Rows;
     public bool Expand => _table.Expand || _table.Width != null;
 
-    protected TableAccessor(Table table, RenderContext options)
+    protected TableAccessor(Table table, RenderOptions options)
     {
         _table = table ?? throw new ArgumentNullException(nameof(table));
         Options = options ?? throw new ArgumentNullException(nameof(options));
diff --git a/src/Spectre.Console/Widgets/Table/TableMeasurer.cs b/src/Spectre.Console/Widgets/Table/TableMeasurer.cs
index d7a26cd..d436a08 100644
--- a/src/Spectre.Console/Widgets/Table/TableMeasurer.cs
+++ b/src/Spectre.Console/Widgets/Table/TableMeasurer.cs
@@ -8,7 +8,7 @@ internal sealed class TableMeasurer : TableAccessor
     private readonly TableBorder _border;
     private readonly bool _padRightCell;
 
-    public TableMeasurer(Table table, RenderContext options)
+    public TableMeasurer(Table table, RenderOptions options)
         : base(table, options)
     {
         _explicitWidth = table.Width;
diff --git a/src/Spectre.Console/Widgets/Table/TableRenderer.cs b/src/Spectre.Console/Widgets/Table/TableRenderer.cs
index fadade9..467fe61 100644
--- a/src/Spectre.Console/Widgets/Table/TableRenderer.cs
+++ b/src/Spectre.Console/Widgets/Table/TableRenderer.cs
@@ -26,7 +26,7 @@ internal static class TableRenderer
             foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate())
             {
                 var justification = context.Columns[columnIndex].Alignment;
-                var childContext = context.Options.WithJustification(justification);
+                var childContext = context.Options with { Justification = justification };
 
                 var lines = Segment.SplitLines(cell.Render(childContext, rowWidth));
                 cellHeight = Math.Max(cellHeight, lines.Count);
@@ -159,7 +159,7 @@ internal static class TableRenderer
         }
 
         var paragraph = new Markup(header.Text, header.Style ?? defaultStyle)
-            .Alignment(Justify.Center)
+            .Justify(Justify.Center)
             .Overflow(Overflow.Ellipsis);
 
         // Render the paragraphs
diff --git a/src/Spectre.Console/Widgets/Table/TableRendererContext.cs b/src/Spectre.Console/Widgets/Table/TableRendererContext.cs
index 17e98c7..b549802 100644
--- a/src/Spectre.Console/Widgets/Table/TableRendererContext.cs
+++ b/src/Spectre.Console/Widgets/Table/TableRendererContext.cs
@@ -31,9 +31,12 @@ internal sealed class TableRendererContext : TableAccessor
     public bool PadRightCell => _table.PadRightCell;
     public TableTitle? Title => _table.Title;
     public TableTitle? Caption => _table.Caption;
-    public Justify? Alignment => _table.Alignment;
 
-    public TableRendererContext(Table table, RenderContext options, IEnumerable<TableRow> rows, int tableWidth, int maxWidth)
+#pragma warning disable CS0618 // Type or member is obsolete
+    public Justify? Alignment => _table.Alignment;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+    public TableRendererContext(Table table, RenderOptions options, IEnumerable<TableRow> rows, int tableWidth, int maxWidth)
         : base(table, options)
     {
         _table = table ?? throw new ArgumentNullException(nameof(table));
diff --git a/src/Spectre.Console/Widgets/Text.cs b/src/Spectre.Console/Widgets/Text.cs
index e84c2f3..d715730 100644
--- a/src/Spectre.Console/Widgets/Text.cs
+++ b/src/Spectre.Console/Widgets/Text.cs
@@ -5,7 +5,7 @@ namespace Spectre.Console;
 /// </summary>
 [DebuggerDisplay("{_text,nq}")]
 [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
-public sealed class Text : Renderable, IAlignable, IOverflowable
+public sealed class Text : Renderable, IHasJustification, IOverflowable
 {
     private readonly Paragraph _paragraph;
 
@@ -32,10 +32,10 @@ public sealed class Text : Renderable, IAlignable, IOverflowable
     /// <summary>
     /// Gets or sets the text alignment.
     /// </summary>
-    public Justify? Alignment
+    public Justify? Justification
     {
-        get => _paragraph.Alignment;
-        set => _paragraph.Alignment = value;
+        get => _paragraph.Justification;
+        set => _paragraph.Justification = value;
     }
 
     /// <summary>
@@ -58,14 +58,14 @@ public sealed class Text : Renderable, IAlignable, IOverflowable
     public int Lines => _paragraph.Lines;
 
     /// <inheritdoc/>
-    protected override Measurement Measure(RenderContext context, int maxWidth)
+    protected override Measurement Measure(RenderOptions options, int maxWidth)
     {
-        return ((IRenderable)_paragraph).Measure(context, maxWidth);
+        return ((IRenderable)_paragraph).Measure(options, maxWidth);
     }
 
     /// <inheritdoc/>
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        return ((IRenderable)_paragraph).Render(context, maxWidth);
+        return ((IRenderable)_paragraph).Render(options, maxWidth);
     }
 }
\ No newline at end of file
diff --git a/src/Spectre.Console/Widgets/TextPath.cs b/src/Spectre.Console/Widgets/TextPath.cs
index 2d03e19..92c8471 100644
--- a/src/Spectre.Console/Widgets/TextPath.cs
+++ b/src/Spectre.Console/Widgets/TextPath.cs
@@ -3,7 +3,7 @@ namespace Spectre.Console;
 /// <summary>
 /// Representation of a file system path.
 /// </summary>
-public sealed class TextPath : IRenderable, IAlignable
+public sealed class TextPath : IRenderable, IHasJustification
 {
     private const string Ellipsis = "...";
     private const string UnicodeEllipsis = "…";
@@ -35,7 +35,7 @@ public sealed class TextPath : IRenderable, IAlignable
     /// <summary>
     /// Gets or sets the alignment.
     /// </summary>
-    public Justify? Alignment { get; set; }
+    public Justify? Justification { get; set; }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="TextPath"/> class.
@@ -66,9 +66,9 @@ public sealed class TextPath : IRenderable, IAlignable
     }
 
     /// <inheritdoc/>
-    public Measurement Measure(RenderContext context, int maxWidth)
+    public Measurement Measure(RenderOptions options, int maxWidth)
     {
-        var fitted = Fit(context, maxWidth);
+        var fitted = Fit(options, maxWidth);
         var separatorCount = fitted.Length - 1;
         var length = fitted.Sum(f => f.Length) + separatorCount;
 
@@ -78,16 +78,14 @@ public sealed class TextPath : IRenderable, IAlignable
     }
 
     /// <inheritdoc/>
-    public IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
-        var alignment = Alignment ?? Justify.Left;
-
         var rootStyle = RootStyle ?? Style.Plain;
         var separatorStyle = SeparatorStyle ?? Style.Plain;
         var stemStyle = StemStyle ?? Style.Plain;
         var leafStyle = LeafStyle ?? Style.Plain;
 
-        var fitted = Fit(context, maxWidth);
+        var fitted = Fit(options, maxWidth);
         var parts = new List<Segment>();
         foreach (var (_, first, last, item) in fitted.Enumerate())
         {
@@ -119,7 +117,7 @@ public sealed class TextPath : IRenderable, IAlignable
         }
 
         // Align the result
-        Aligner.Align(parts, Alignment, maxWidth);
+        Aligner.Align(parts, Justification, maxWidth);
 
         // Insert a line break
         parts.Add(Segment.LineBreak);
@@ -127,7 +125,7 @@ public sealed class TextPath : IRenderable, IAlignable
         return parts;
     }
 
-    private string[] Fit(RenderContext context, int maxWidth)
+    private string[] Fit(RenderOptions options, int maxWidth)
     {
         // No parts?
         if (_parts.Length == 0)
@@ -141,7 +139,7 @@ public sealed class TextPath : IRenderable, IAlignable
             return _parts;
         }
 
-        var ellipsis = context.Unicode ? UnicodeEllipsis : Ellipsis;
+        var ellipsis = options.Unicode ? UnicodeEllipsis : Ellipsis;
         var ellipsisLength = Cell.GetCellLength(ellipsis);
 
         if (_parts.Length >= 2)
diff --git a/src/Spectre.Console/Widgets/Tree.cs b/src/Spectre.Console/Widgets/Tree.cs
index 9929a53..289884e 100644
--- a/src/Spectre.Console/Widgets/Tree.cs
+++ b/src/Spectre.Console/Widgets/Tree.cs
@@ -47,7 +47,7 @@ public sealed class Tree : Renderable, IHasTreeNodes
     }
 
     /// <inheritdoc />
-    protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
+    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
     {
         var result = new List<Segment>();
         var visitedNodes = new HashSet<TreeNode>();
@@ -56,7 +56,7 @@ public sealed class Tree : Renderable, IHasTreeNodes
         stack.Push(new Queue<TreeNode>(new[] { _root }));
 
         var levels = new List<Segment>();
-        levels.Add(GetGuide(context, TreeGuidePart.Continue));
+        levels.Add(GetGuide(options, TreeGuidePart.Continue));
 
         while (stack.Count > 0)
         {
@@ -66,7 +66,7 @@ public sealed class Tree : Renderable, IHasTreeNodes
                 levels.RemoveLast();
                 if (levels.Count > 0)
                 {
-                    levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.Fork));
+                    levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.Fork));
                 }
 
                 continue;
@@ -83,11 +83,11 @@ public sealed class Tree : Renderable, IHasTreeNodes
 
             if (isLastChild)
             {
-                levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.End));
+                levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.End));
             }
 
             var prefix = levels.Skip(1).ToList();
-            var renderableLines = Segment.SplitLines(current.Renderable.Render(context, maxWidth - Segment.CellCount(prefix)));
+            var renderableLines = Segment.SplitLines(current.Renderable.Render(options, maxWidth - Segment.CellCount(prefix)));
 
             foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate())
             {
@@ -102,14 +102,14 @@ public sealed class Tree : Renderable, IHasTreeNodes
                 if (isFirstLine && prefix.Count > 0)
                 {
                     var part = isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue;
-                    prefix.AddOrReplaceLast(GetGuide(context, part));
+                    prefix.AddOrReplaceLast(GetGuide(options, part));
                 }
             }
 
             if (current.Expanded && current.Nodes.Count > 0)
             {
-                levels.AddOrReplaceLast(GetGuide(context, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue));
-                levels.Add(GetGuide(context, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork));
+                levels.AddOrReplaceLast(GetGuide(options, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue));
+                levels.Add(GetGuide(options, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork));
 
                 stack.Push(new Queue<TreeNode>(current.Nodes));
             }
@@ -118,9 +118,9 @@ public sealed class Tree : Renderable, IHasTreeNodes
         return result;
     }
 
-    private Segment GetGuide(RenderContext context, TreeGuidePart part)
+    private Segment GetGuide(RenderOptions options, TreeGuidePart part)
     {
-        var guide = Guide.GetSafeTreeGuide(safe: !context.Unicode);
+        var guide = Guide.GetSafeTreeGuide(safe: !options.Unicode);
         return new Segment(guide.GetPart(part), Style ?? Style.Plain);
     }
 }
\ No newline at end of file
diff --git a/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj b/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj
index 67f5874..6f68434 100644
--- a/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj
+++ b/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj
@@ -1,8 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">net7.0;net48</TargetFrameworks>
-    <TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('Windows'))">net7.0</TargetFrameworks>
+    <TargetFrameworks>net7.0</TargetFrameworks>
   </PropertyGroup>
 
   <ItemGroup>
@@ -30,15 +29,4 @@
     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <None Update="Expectations\Help\Description_No_Trailing_Period.Output.received.txt">
-      <ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
-      <DependentUpon>%(ParentFile).cs</DependentUpon>
-    </None>
-    <None Update="Expectations\Help\Description_No_Trailing_Period.Output.verified.txt">
-      <ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
-      <DependentUpon>%(ParentFile).cs</DependentUpon>
-    </None>
-  </ItemGroup>
-
 </Project>
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Bottom.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Bottom.Output.verified.txt
new file mode 100644
index 0000000..74ab31e
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Bottom.Output.verified.txt
@@ -0,0 +1,15 @@
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+            ┌──────────────┐            
+            │ Hello World! │            
+            └──────────────┘            
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Middle.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Middle.Output.verified.txt
new file mode 100644
index 0000000..33cc191
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Middle.Output.verified.txt
@@ -0,0 +1,15 @@
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+            ┌──────────────┐            
+            │ Hello World! │            
+            └──────────────┘            
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Top.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Top.Output.verified.txt
new file mode 100644
index 0000000..7390030
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Center_Top.Output.verified.txt
@@ -0,0 +1,15 @@
+            ┌──────────────┐            
+            │ Hello World! │            
+            └──────────────┘            
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Bottom.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Bottom.Output.verified.txt
new file mode 100644
index 0000000..5c9e9c0
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Bottom.Output.verified.txt
@@ -0,0 +1,15 @@
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+┌──────────────┐                        
+│ Hello World! │                        
+└──────────────┘                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Middle.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Middle.Output.verified.txt
new file mode 100644
index 0000000..f7beea5
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Middle.Output.verified.txt
@@ -0,0 +1,15 @@
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+┌──────────────┐                        
+│ Hello World! │                        
+└──────────────┘                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Top.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Top.Output.verified.txt
new file mode 100644
index 0000000..21d696c
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Left_Top.Output.verified.txt
@@ -0,0 +1,15 @@
+┌──────────────┐                        
+│ Hello World! │                        
+└──────────────┘                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Bottom.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Bottom.Output.verified.txt
new file mode 100644
index 0000000..d61cd86
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Bottom.Output.verified.txt
@@ -0,0 +1,15 @@
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                        ┌──────────────┐
+                        │ Hello World! │
+                        └──────────────┘
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Middle.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Middle.Output.verified.txt
new file mode 100644
index 0000000..90a5884
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Middle.Output.verified.txt
@@ -0,0 +1,15 @@
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                        ┌──────────────┐
+                        │ Hello World! │
+                        └──────────────┘
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Top.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Top.Output.verified.txt
new file mode 100644
index 0000000..a1742a8
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Align/Right_Top.Output.verified.txt
@@ -0,0 +1,15 @@
+                        ┌──────────────┐
+                        │ Hello World! │
+                        └──────────────┘
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
+                                        
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Empty_Layout.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Empty_Layout.Output.verified.txt
new file mode 100644
index 0000000..5caaedb
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Empty_Layout.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─40 x 15──────────────────────────────╮
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│             Placeholder              │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+╰──────────────────────────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Fallback_Layout.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Fallback_Layout.Output.verified.txt
new file mode 100644
index 0000000..0df2d79
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Fallback_Layout.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─T1 (40 x 7)──────────────────────────╮
+│                                      │
+│                                      │
+│             Placeholder              │
+│                                      │
+│                                      │
+╰──────────────────────────────────────╯
+╭─T2 (40 x 8)──────────────────────────╮
+│                                      │
+│                                      │
+│             Placeholder              │
+│                                      │
+│                                      │
+│                                      │
+╰──────────────────────────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout.Output.verified.txt
new file mode 100644
index 0000000..614b3a1
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout.Output.verified.txt
@@ -0,0 +1,15 @@
+╔══════════════════════════════════════╗
+║ Hello                                ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+║                                      ║
+╚══════════════════════════════════════╝
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Columns.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Columns.Output.verified.txt
new file mode 100644
index 0000000..5fea3f1
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Columns.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─Left (20 x 15)───╮╭─Right (20 x 15)──╮
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│   Placeholder    ││   Placeholder    │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+│                  ││                  │
+╰──────────────────╯╰──────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Columns.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Columns.Output.verified.txt
new file mode 100644
index 0000000..9c7084a
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Columns.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─L1…────╮╭─L2…────╮╭─R1…────╮╭─R2…────╮
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│ Placeh ││ Placeh ││ Placeh ││ Placeh │
+│ older  ││ older  ││ older  ││ older  │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+│        ││        ││        ││        │
+╰────────╯╰────────╯╰────────╯╰────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows.Output.verified.txt
new file mode 100644
index 0000000..bb5f7d9
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─T1 (40 x 3)──────────────────────────╮
+│             Placeholder              │
+╰──────────────────────────────────────╯
+╭─T2 (40 x 4)──────────────────────────╮
+│             Placeholder              │
+│                                      │
+╰──────────────────────────────────────╯
+╭─B1 (40 x 4)──────────────────────────╮
+│             Placeholder              │
+│                                      │
+╰──────────────────────────────────────╯
+╭─B2 (40 x 4)──────────────────────────╮
+│             Placeholder              │
+│                                      │
+╰──────────────────────────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows_And_Columns.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows_And_Columns.Output.verified.txt
new file mode 100644
index 0000000..258288b
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Nested_Rows_And_Columns.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─A (20 x 3)───────╮╭─B (20 x 3)───────╮
+│   Placeholder    ││   Placeholder    │
+╰──────────────────╯╰──────────────────╯
+╭─C (20 x 4)───────╮╭─D (20 x 4)───────╮
+│   Placeholder    ││   Placeholder    │
+│                  ││                  │
+╰──────────────────╯╰──────────────────╯
+╭─E (20 x 4)───────╮╭─F (20 x 4)───────╮
+│   Placeholder    ││   Placeholder    │
+│                  ││                  │
+╰──────────────────╯╰──────────────────╯
+╭─G (20 x 4)───────╮╭─H (20 x 4)───────╮
+│   Placeholder    ││   Placeholder    │
+│                  ││                  │
+╰──────────────────╯╰──────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Minimum_Size.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Minimum_Size.Output.verified.txt
new file mode 100644
index 0000000..5a21d74
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Minimum_Size.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─Left (30 x 15)─────────────╮╭─Right…─╮
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││ Placeh │
+│        Placeholder         ││ older  │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+╰────────────────────────────╯╰────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Ratio.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Ratio.Output.verified.txt
new file mode 100644
index 0000000..5a21d74
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Ratio.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─Left (30 x 15)─────────────╮╭─Right…─╮
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││ Placeh │
+│        Placeholder         ││ older  │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+│                            ││        │
+╰────────────────────────────╯╰────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Size.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Size.Output.verified.txt
new file mode 100644
index 0000000..6ffd309
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Respect_To_Size.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─Left (28 x 15)───────────╮╭─Right…───╮
+│                          ││          │
+│                          ││          │
+│                          ││          │
+│                          ││          │
+│                          ││          │
+│                          ││ Placehol │
+│       Placeholder        ││   der    │
+│                          ││          │
+│                          ││          │
+│                          ││          │
+│                          ││          │
+│                          ││          │
+│                          ││          │
+╰──────────────────────────╯╰──────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Rows.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Rows.Output.verified.txt
new file mode 100644
index 0000000..12a6f9d
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Rows.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─Top (40 x 7)─────────────────────────╮
+│                                      │
+│                                      │
+│             Placeholder              │
+│                                      │
+│                                      │
+╰──────────────────────────────────────╯
+╭─Bottom (40 x 8)──────────────────────╮
+│                                      │
+│                                      │
+│             Placeholder              │
+│                                      │
+│                                      │
+│                                      │
+╰──────────────────────────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_Without_Invisible_Children.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_Without_Invisible_Children.Output.verified.txt
new file mode 100644
index 0000000..9468df9
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_Without_Invisible_Children.Output.verified.txt
@@ -0,0 +1,15 @@
+╭─B (40 x 3)───────────────────────────╮
+│             Placeholder              │
+╰──────────────────────────────────────╯
+╭─C (20 x 4)───────╮╭─D (20 x 4)───────╮
+│   Placeholder    ││   Placeholder    │
+│                  ││                  │
+╰──────────────────╯╰──────────────────╯
+╭─E (20 x 4)───────╮╭─F (20 x 4)───────╮
+│   Placeholder    ││   Placeholder    │
+│                  ││                  │
+╰──────────────────╯╰──────────────────╯
+╭─G (40 x 4)───────────────────────────╮
+│             Placeholder              │
+│                                      │
+╰──────────────────────────────────────╯
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Height.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Height.Output.verified.txt
new file mode 100644
index 0000000..554308f
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Height.Output.verified.txt
@@ -0,0 +1,25 @@
+┌───────────────────┐
+│ Hello World       │
+│ Hello Hello Hello │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+│                   │
+└───────────────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width.Output.verified.txt
new file mode 100644
index 0000000..dc133c3
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width.Output.verified.txt
@@ -0,0 +1,3 @@
+┌───────────────────────┐
+│ Hello World           │
+└───────────────────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_Height.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_Height.Output.verified.txt
new file mode 100644
index 0000000..2f85980
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_Height.Output.verified.txt
@@ -0,0 +1,25 @@
+┌────────────────────────────────────────────────┐
+│ Hello World                                    │
+│ Hello Hello Hello                              │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+│                                                │
+└────────────────────────────────────────────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_MaxWidth.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_MaxWidth.Output.verified.txt
new file mode 100644
index 0000000..21ceda9
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Panel/Render_Width_MaxWidth.Output.verified.txt
@@ -0,0 +1,3 @@
+┌──────────────────┐
+│ Hello World      │
+└──────────────────┘
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Centered.Align_Widget.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Centered.Align_Widget.verified.txt
new file mode 100644
index 0000000..62617a0
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_Centered.Align_Widget.verified.txt
@@ -0,0 +1,6 @@
+                          ┌────────┬────────┬───────┐                           
+                          │ Foo    │ Bar    │ Baz   │                           
+                          ├────────┼────────┼───────┤                           
+                          │ Qux    │ Corgi  │ Waldo │                           
+                          │ Grault │ Garply │ Fred  │                           
+                          └────────┴────────┴───────┘                           
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_LeftAligned.Align_Widget.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_LeftAligned.Align_Widget.verified.txt
new file mode 100644
index 0000000..6edca8f
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_LeftAligned.Align_Widget.verified.txt
@@ -0,0 +1,6 @@
+┌────────┬────────┬───────┐                                                     
+│ Foo    │ Bar    │ Baz   │                                                     
+├────────┼────────┼───────┤                                                     
+│ Qux    │ Corgi  │ Waldo │                                                     
+│ Grault │ Garply │ Fred  │                                                     
+└────────┴────────┴───────┘                                                     
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_RightAligned.Align_Widget.verified.txt b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_RightAligned.Align_Widget.verified.txt
new file mode 100644
index 0000000..72a459e
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/Widgets/Table/Render_RightAligned.Align_Widget.verified.txt
@@ -0,0 +1,6 @@
+                                                     ┌────────┬────────┬───────┐
+                                                     │ Foo    │ Bar    │ Baz   │
+                                                     ├────────┼────────┼───────┤
+                                                     │ Qux    │ Corgi  │ Waldo │
+                                                     │ Grault │ Garply │ Fred  │
+                                                     └────────┴────────┴───────┘
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj b/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj
index 0342253..4d74810 100644
--- a/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj
+++ b/test/Spectre.Console.Tests/Spectre.Console.Tests.csproj
@@ -1,8 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">net7.0;net48</TargetFrameworks>
-    <TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('Windows'))">net7.0</TargetFrameworks>
+    <TargetFrameworks>net7.0</TargetFrameworks>
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressColumnFixture.cs b/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressColumnFixture.cs
index 4b10877..9a2fac2 100644
--- a/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressColumnFixture.cs
+++ b/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressColumnFixture.cs
@@ -16,7 +16,7 @@ public sealed class ProgressColumnFixture<T>
     public string Render()
     {
         var console = new TestConsole();
-        var context = new RenderContext(console.Profile.Capabilities);
+        var context = RenderOptions.Create(console, console.Profile.Capabilities);
         console.Write(Column.Render(context, Task, TimeSpan.Zero));
         return console.Output;
     }
diff --git a/test/Spectre.Console.Tests/Unit/Rendering/RenderHookTests.cs b/test/Spectre.Console.Tests/Unit/Rendering/RenderHookTests.cs
index 219a13d..6be28da 100644
--- a/test/Spectre.Console.Tests/Unit/Rendering/RenderHookTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Rendering/RenderHookTests.cs
@@ -4,7 +4,7 @@ public sealed class RenderHookTests
 {
     private sealed class HelloRenderHook : IRenderHook
     {
-        public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
+        public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables)
         {
             return new IRenderable[] { new Text("Hello\n") }.Concat(renderables);
         }
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/AlignTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/AlignTests.cs
new file mode 100644
index 0000000..e3f2923
--- /dev/null
+++ b/test/Spectre.Console.Tests/Unit/Widgets/AlignTests.cs
@@ -0,0 +1,155 @@
+using Spectre.Console.Extensions;
+
+namespace Spectre.Console.Tests.Unit;
+
+[UsesVerify]
+[ExpectationPath("Widgets/Align")]
+public sealed class AlignTests
+{
+    [UsesVerify]
+    public sealed class Left
+    {
+        [Fact]
+        [Expectation("Left_Top")]
+        public Task Should_Render_Panel_Left_Aligned_At_Top()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Left(new Panel("Hello World!"), VerticalAlignment.Top).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("Left_Middle")]
+        public Task Should_Render_Panel_Left_Aligned_At_Middle()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Left(new Panel("Hello World!"), VerticalAlignment.Middle).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("Left_Bottom")]
+        public Task Should_Render_Panel_Left_Aligned_At_Bottom()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Left(new Panel("Hello World!"), VerticalAlignment.Bottom).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+    }
+
+    [UsesVerify]
+    public sealed class Center
+    {
+        [Fact]
+        [Expectation("Center_Top")]
+        public Task Should_Render_Panel_Center_Aligned_At_Top()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Center(new Panel("Hello World!"), VerticalAlignment.Top).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("Center_Middle")]
+        public Task Should_Render_Panel_Center_Aligned_At_Middle()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Center(new Panel("Hello World!"), VerticalAlignment.Middle).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("Center_Bottom")]
+        public Task Should_Render_Panel_Center_Aligned_At_Bottom()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Center(new Panel("Hello World!"), VerticalAlignment.Bottom).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+    }
+
+    [UsesVerify]
+    public sealed class Right
+    {
+        [Fact]
+        [Expectation("Right_Top")]
+        public Task Should_Render_Panel_Right_Aligned_At_Top()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Right(new Panel("Hello World!"), VerticalAlignment.Top).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("Right_Middle")]
+        public Task Should_Render_Panel_Right_Aligned_At_Middle()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Right(new Panel("Hello World!"), VerticalAlignment.Middle).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+
+        [Fact]
+        [Expectation("Right_Bottom")]
+        public Task Should_Render_Panel_Right_Aligned_At_Bottom()
+        {
+            // Given
+            var console = new TestConsole().Size(new Size(40, 15));
+            var align = Align.Right(new Panel("Hello World!"), VerticalAlignment.Bottom).Height(15);
+
+            // When
+            console.Write(align);
+
+            // Then
+            return Verifier.Verify(console.Output);
+        }
+    }
+}
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/FigletTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/FigletTests.cs
index 8453901..b283176 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/FigletTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/FigletTests.cs
@@ -60,7 +60,7 @@ public sealed class FigletTests
         // Given
         var console = new TestConsole().Width(120);
         var text = new FigletText(FigletFont.Default, "Spectre.Console")
-            .Alignment(Justify.Left);
+            .Justify(Justify.Left);
 
         // When
         console.Write(text);
@@ -76,7 +76,7 @@ public sealed class FigletTests
         // Given
         var console = new TestConsole().Width(120);
         var text = new FigletText(FigletFont.Default, "Spectre.Console")
-            .Alignment(Justify.Center);
+            .Justify(Justify.Center);
 
         // When
         console.Write(text);
@@ -92,7 +92,7 @@ public sealed class FigletTests
         // Given
         var console = new TestConsole().Width(120);
         var text = new FigletText(FigletFont.Default, "Spectre.Console")
-            .Alignment(Justify.Right);
+            .Justify(Justify.Right);
 
         // When
         console.Write(text);
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/LayoutTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/LayoutTests.cs
new file mode 100644
index 0000000..fc0017e
--- /dev/null
+++ b/test/Spectre.Console.Tests/Unit/Widgets/LayoutTests.cs
@@ -0,0 +1,268 @@
+namespace Spectre.Console.Tests.Unit;
+
+[UsesVerify]
+[ExpectationPath("Widgets/Layout")]
+public sealed class LayoutTests
+{
+    [Fact]
+    [Expectation("Render_Empty_Layout")]
+    public Task Should_Render_Empty_Layout()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout();
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout")]
+    public Task Should_Render_Empty_Layout_With_Renderable()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout().Update(new Panel("Hello").DoubleBorder().Expand());
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Columns")]
+    public Task Should_Render_Layout_With_Columns()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitColumns(
+                new Layout("Left"),
+                new Layout("Right"));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Rows")]
+    public Task Should_Render_Layout_With_Rows()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitRows(
+                new Layout("Top"),
+                new Layout("Bottom"));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Nested_Columns")]
+    public Task Should_Render_Layout_With_Nested_Columns()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitColumns(
+                new Layout("Left")
+                    .SplitColumns(
+                        new Layout("L1"),
+                        new Layout("L2")),
+                new Layout("Right")
+                    .SplitColumns(
+                        new Layout("R1"),
+                        new Layout("R2")));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Nested_Rows")]
+    public Task Should_Render_Layout_With_Nested_Rows()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitRows(
+                new Layout("Top")
+                    .SplitRows(
+                        new Layout("T1"),
+                        new Layout("T2")),
+                new Layout("Bottom")
+                    .SplitRows(
+                        new Layout("B1"),
+                        new Layout("B2")));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Nested_Rows_And_Columns")]
+    public Task Should_Render_Layout_With_Nested_Rows_And_Columns()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitRows(
+                new Layout("Top")
+                    .SplitRows(
+                        new Layout("T1")
+                            .SplitColumns(
+                                new Layout("A"),
+                                new Layout("B")),
+                        new Layout("T2")
+                            .SplitColumns(
+                                new Layout("C"),
+                                new Layout("D"))),
+                new Layout("Bottom")
+                    .SplitRows(
+                        new Layout("B1")
+                            .SplitColumns(
+                                new Layout("E"),
+                                new Layout("F")),
+                        new Layout("B2")
+                            .SplitColumns(
+                                    new Layout("G"),
+                                    new Layout("H"))));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_Without_Invisible_Children")]
+    public Task Should_Render_Layout_Without_Invisible_Children()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitRows(
+                new Layout("Top")
+                    .SplitRows(
+                        new Layout("T1")
+                            .SplitColumns(
+                                new Layout("A").Invisible(),
+                                new Layout("B")),
+                        new Layout("T2")
+                            .SplitColumns(
+                                new Layout("C"),
+                                new Layout("D"))),
+                new Layout("Bottom")
+                    .SplitRows(
+                        new Layout("B1")
+                            .SplitColumns(
+                                new Layout("E"),
+                                new Layout("F")),
+                        new Layout("B2")
+                            .SplitColumns(
+                                    new Layout("G"),
+                                    new Layout("H").Invisible())));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Respect_To_Ratio")]
+    public Task Should_Render_Layout_With_Respect_To_Ratio()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitColumns(
+                new Layout("Left").Ratio(3),
+                new Layout("Right"));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Respect_To_Size")]
+    public Task Should_Render_Layout_With_Respect_To_Size()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitColumns(
+                new Layout("Left").Size(28),
+                new Layout("Right"));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Layout_With_Respect_To_Minimum_Size")]
+    public Task Should_Render_Layout_With_Respect_To_Minimum_Size()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitColumns(
+                new Layout("Left").Size(28).MinimumSize(30),
+                new Layout("Right"));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Fallback_Layout")]
+    public Task Should_Fall_Back_To_Parent_Layout_If_All_Children_Are_Invisible()
+    {
+        // Given
+        var console = new TestConsole().Size(new Size(40, 15));
+        var layout = new Layout()
+            .SplitRows(
+                new Layout("T1").SplitColumns(
+                    new Layout("A").Invisible(),
+                    new Layout("B").Invisible()),
+                new Layout("T2").SplitColumns(
+                    new Layout("C").Invisible(),
+                    new Layout("D").Invisible()));
+
+        // When
+        console.Write(layout);
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+}
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/PanelTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/PanelTests.cs
index 73a2ca9..377c94f 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/PanelTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/PanelTests.cs
@@ -81,7 +81,7 @@ public sealed class PanelTests
         // When
         console.Write(new Panel("Hello World")
         {
-            Header = new PanelHeader("Greeting").LeftAligned(),
+            Header = new PanelHeader("Greeting").LeftJustified(),
             Expand = true,
         });
 
@@ -117,7 +117,7 @@ public sealed class PanelTests
         // When
         console.Write(new Panel("Hello World")
         {
-            Header = new PanelHeader("Greeting").RightAligned(),
+            Header = new PanelHeader("Greeting").RightJustified(),
             Expand = true,
         });
 
@@ -204,6 +204,76 @@ public sealed class PanelTests
         return Verifier.Verify(console.Output);
     }
 
+    [Fact]
+    [Expectation("Render_Width")]
+    public Task Should_Render_To_Specified_Width()
+    {
+        // Given
+        var console = new TestConsole();
+
+        // When
+        console.Write(new Panel(new Text("Hello World"))
+        {
+            Width = 25,
+        });
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Width_MaxWidth")]
+    public Task Should_Use_Max_Width_If_Specified_Width_Is_Too_Large()
+    {
+        // Given
+        var console = new TestConsole();
+        console.Profile.Width = 20;
+
+        // When
+        console.Write(new Panel(new Text("Hello World"))
+        {
+            Width = 25,
+        });
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Height")]
+    public Task Should_Render_To_Specified_Height()
+    {
+        // Given
+        var console = new TestConsole();
+
+        // When
+        console.Write(new Panel(new Text("Hello World\nHello Hello Hello"))
+        {
+            Height = 25,
+        });
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
+    [Fact]
+    [Expectation("Render_Width_Height")]
+    public Task Should_Render_To_Specified_Width_And_Height()
+    {
+        // Given
+        var console = new TestConsole();
+
+        // When
+        console.Write(new Panel("Hello World\nHello Hello Hello")
+        {
+            Width = 50,
+            Height = 25,
+        });
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
     [Fact]
     [Expectation("Render_Child_RightAligned")]
     public Task Should_Justify_Child_To_Right_Correctly()
@@ -213,7 +283,7 @@ public sealed class PanelTests
 
         // When
         console.Write(
-            new Panel(new Text("Hello World").RightAligned())
+            new Panel(new Text("Hello World").RightJustified())
             {
                 Expand = true,
             });
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/RuleTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/RuleTests.cs
index fbff7b5..fada3fd 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/RuleTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/RuleTests.cs
@@ -70,7 +70,7 @@ public sealed class RuleTests
         // When
         console.Write(new Rule("Hello World")
         {
-            Alignment = Justify.Left,
+            Justification = Justify.Left,
         });
 
         // Then
@@ -87,7 +87,7 @@ public sealed class RuleTests
         // When
         console.Write(new Rule("Hello World")
         {
-            Alignment = Justify.Right,
+            Justification = Justify.Right,
         });
 
         // Then
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
index 2716fbb..a027658 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/Table/TableTests.cs
@@ -187,7 +187,9 @@ public sealed class TableTests
         // Given
         var console = new TestConsole();
         var table = new Table();
+#pragma warning disable CS0618 // Type or member is obsolete
         table.Alignment = Justify.Left;
+#pragma warning restore CS0618 // Type or member is obsolete
         table.AddColumns("Foo", "Bar", "Baz");
         table.AddRow("Qux", "Corgi", "Waldo");
         table.AddRow("Grault", "Garply", "Fred");
@@ -199,6 +201,24 @@ public sealed class TableTests
         return Verifier.Verify(console.Output);
     }
 
+    [Fact]
+    [Expectation("Render_LeftAligned", "Align_Widget")]
+    public Task Should_Left_Align_Table_Correctly_When_Wrapped_In_Align_Widget()
+    {
+        // Given
+        var console = new TestConsole();
+        var table = new Table();
+        table.AddColumns("Foo", "Bar", "Baz");
+        table.AddRow("Qux", "Corgi", "Waldo");
+        table.AddRow("Grault", "Garply", "Fred");
+
+        // When
+        console.Write(Align.Left(table));
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
     [Fact]
     [Expectation("Render_Centered")]
     public Task Should_Center_Table_Correctly()
@@ -206,7 +226,9 @@ public sealed class TableTests
         // Given
         var console = new TestConsole();
         var table = new Table();
+#pragma warning disable CS0618 // Type or member is obsolete
         table.Alignment = Justify.Center;
+#pragma warning restore CS0618 // Type or member is obsolete
         table.AddColumns("Foo", "Bar", "Baz");
         table.AddRow("Qux", "Corgi", "Waldo");
         table.AddRow("Grault", "Garply", "Fred");
@@ -218,6 +240,24 @@ public sealed class TableTests
         return Verifier.Verify(console.Output);
     }
 
+    [Fact]
+    [Expectation("Render_Centered", "Align_Widget")]
+    public Task Should_Center_Table_Correctly_When_Wrapped_In_Align_Widget()
+    {
+        // Given
+        var console = new TestConsole();
+        var table = new Table();
+        table.AddColumns("Foo", "Bar", "Baz");
+        table.AddRow("Qux", "Corgi", "Waldo");
+        table.AddRow("Grault", "Garply", "Fred");
+
+        // When
+        console.Write(Align.Center(table));
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
     [Fact]
     [Expectation("Render_RightAligned")]
     public Task Should_Right_Align_Table_Correctly()
@@ -225,7 +265,9 @@ public sealed class TableTests
         // Given
         var console = new TestConsole();
         var table = new Table();
+#pragma warning disable CS0618 // Type or member is obsolete
         table.Alignment = Justify.Right;
+#pragma warning restore CS0618 // Type or member is obsolete
         table.AddColumns("Foo", "Bar", "Baz");
         table.AddRow("Qux", "Corgi", "Waldo");
         table.AddRow("Grault", "Garply", "Fred");
@@ -237,6 +279,24 @@ public sealed class TableTests
         return Verifier.Verify(console.Output);
     }
 
+    [Fact]
+    [Expectation("Render_RightAligned", "Align_Widget")]
+    public Task Should_Right_Align_Table_Correctly_When_Wrapped_In_Align_Widget()
+    {
+        // Given
+        var console = new TestConsole();
+        var table = new Table();
+        table.AddColumns("Foo", "Bar", "Baz");
+        table.AddRow("Qux", "Corgi", "Waldo");
+        table.AddRow("Grault", "Garply", "Fred");
+
+        // When
+        console.Write(Align.Right(table));
+
+        // Then
+        return Verifier.Verify(console.Output);
+    }
+
     [Fact]
     [Expectation("Render_Nested")]
     public Task Should_Render_Table_Nested_In_Panels_Correctly()
@@ -380,7 +440,7 @@ public sealed class TableTests
         table.AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green)));
         table.AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue)));
         table.AddRow(new Text("Hello").Centered(), new Markup("[red]World[/]"), Text.Empty);
-        table.AddRow(second, new Text("Whaat"), new Text("Lol").RightAligned());
+        table.AddRow(second, new Text("Whaat"), new Text("Lol").RightJustified());
         table.AddRow(new Markup("[blue]Hej[/]"), new Markup("[yellow]Världen[/]"), Text.Empty);
 
         // When
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/TextPathTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/TextPathTests.cs
index 0f984be..d7e5513 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/TextPathTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/TextPathTests.cs
@@ -77,7 +77,7 @@ public sealed class TextPathTests
         var console = new TestConsole().Width(40);
 
         // When
-        console.Write(new TextPath("C:/My documents/Bar/Baz.txt").RightAligned());
+        console.Write(new TextPath("C:/My documents/Bar/Baz.txt").RightJustified());
 
         // Then
         console.Output.TrimEnd('\n')
diff --git a/test/Spectre.Console.Tests/Unit/Widgets/TextTests.cs b/test/Spectre.Console.Tests/Unit/Widgets/TextTests.cs
index cf08e23..6b0bdfd 100644
--- a/test/Spectre.Console.Tests/Unit/Widgets/TextTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Widgets/TextTests.cs
@@ -42,11 +42,13 @@ public sealed class TextTests
     public void Should_Consider_The_Longest_Word_As_Minimum_Width()
     {
         // Given
+        var console = new TestConsole();
         var caps = new TestCapabilities { Unicode = true };
         var text = new Text("Foo Bar Baz\nQux\nLol mobile");
 
         // When
-        var result = ((IRenderable)text).Measure(caps.CreateRenderContext(), 80);
+        var result = ((IRenderable)text).Measure(
+            caps.CreateRenderContext(console), 80);
 
         // Then
         result.Min.ShouldBe(6);
@@ -56,11 +58,13 @@ public sealed class TextTests
     public void Should_Consider_The_Longest_Line_As_Maximum_Width()
     {
         // Given
+        var console = new TestConsole();
         var caps = new TestCapabilities { Unicode = true };
         var text = new Text("Foo Bar Baz\nQux\nLol mobile");
 
         // When
-        var result = ((IRenderable)text).Measure(caps.CreateRenderContext(), 80);
+        var result = ((IRenderable)text).Measure(
+            caps.CreateRenderContext(console), 80);
 
         // Then
         result.Max.ShouldBe(11);