Repository: Textualize/textual
Pull request: Add containers
**Please review the following checklist.**

- [x] Docstrings on all new or modified functions / classes 
- [x] Updated documentation
- [x] Updated CHANGELOG.md (where appropriate)


Problem statement:
Additional containers
We need a few additions / modifications to containers.py

- `Center` to horizontally align contents (width 100%, height auto)
- `Middle` to vertically align contents.
- `Horizontal` should have scrollbars disabled by default
- `Vertical` should have scrollbars disabled by default
- `HorizontalScroll` should have horizontal layout and scrollbars auto
- `VerticalScroll` should have vertical layout and scrollbars auto

Hints from issue discussion:
There's some text about whether `Horizontal` and `Vertical` have scrollbars enabled or not in the "Layout" page of the guide. We'll need to make sure we update that page if we change the behaviour.
Trying to think as someone who just found Textual, a container called `Vertical` seems to be the simplest vertical container there is _and_ it seems perfectly reasonable to expect that container to have scrollbars by default.
In fact, the name `Vertical` only implies a specific layout and it does not imply a change to the value of `overflow`, which is `auto` by default.

Why don't we let `Horizontal` and `Vertical` have their scrollbars by default, and add something like `HorizontalNoScroll` and `VerticalNoScroll` instead?
The problem is that people liberally add these containers, but they generally only want a single container to scroll. 

If they mess up the styling, and everyone will first attempt, it can be very confusing when you get a scrollbar you don't want. Because you don't know which container really is scrolling.

I think its easier to design a UI if you have to explicitly say which container(s) should scroll.
Then, should the default value of `overflow` be `overflow: hidden hidden`?
@willmcgugan what non-overflowing version are you going for?

 1. don't overflow in either direction:
    - `Horizontal` and `Vertical` set `overflow: hidden hidden`.
 2. don't overflow in the direction of the layout:
    - `Horizontal` sets `overflow-x: hidden`; and
    - `Vertical` sets `overflow-y: hidden`.
Also, do you literally mean for `Center` to align **contents**, or should it just align _children_?
(Ditto for `Middle`.)
(not answering for Will, of course) I think it would only make sense for it to act on the immediate children What's being spoken about here is sort of a follow on from [this FAQ](https://github.com/Textualize/textual/blob/main/questions/align-center-middle.question.md).
> @willmcgugan what non-overflowing version are you going for?
> 
> 1. don't overflow in either direction:
>    
>    * `Horizontal` and `Vertical` set `overflow: hidden hidden`.

This one.


`Center` should align children.



@willmcgugan I think it is more intuitive if the default styling of `Center`/`Middle` is to have their relevant dimension be `1fr` instead of `100%` to fill all the space available instead of being forcefully 100% of the container.

Let me know your decision and then I am ready to PR this.
Leave the Center / Middle dimensions as blank, as it will compensate for margin.

Commit message: Update snapshot tests.

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e7bd48788..d65d0199c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,14 +7,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## Unreleased
 
-### Fixed
+### Changed
 
-- Fixed container not resizing when a widget is removed https://github.com/Textualize/textual/issues/2007
-- Fixes issue where the horizontal scrollbar would be incorrectly enabled https://github.com/Textualize/textual/pull/2024
+- Breaking change: changed default behaviour of `Vertical` (see `VerticalScroll`) https://github.com/Textualize/textual/issues/1957
+- The default `overflow` style for `Horizontal` was changed to `hidden hidden` https://github.com/Textualize/textual/issues/1957
 
 ### Added
 
 - Added a LoadingIndicator widget https://github.com/Textualize/textual/pull/2018
+- Added `HorizontalScroll` https://github.com/Textualize/textual/issues/1957
+- Added `Center` https://github.com/Textualize/textual/issues/1957
+- Added `Middle` https://github.com/Textualize/textual/issues/1957
+- Added `VerticalScroll` (mimicking the old behaviour of `Vertical`) https://github.com/Textualize/textual/issues/1957
+
+### Fixed
+
+- Fixed container not resizing when a widget is removed https://github.com/Textualize/textual/issues/2007
+- Fixes issue where the horizontal scrollbar would be incorrectly enabled https://github.com/Textualize/textual/pull/2024
 
 ## [0.14.0] - 2023-03-09
 
diff --git a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py
index 6e0ab09492..9773da0c92 100644
--- a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py
+++ b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking01.py
@@ -1,9 +1,9 @@
-from random import randint
 import time
+from random import randint
 
 from textual.app import App, ComposeResult
 from textual.color import Color
-from textual.containers import Grid, Vertical
+from textual.containers import Grid, VerticalScroll
 from textual.widget import Widget
 from textual.widgets import Footer, Label
 
@@ -28,7 +28,7 @@ class MyApp(App[None]):
     def compose(self) -> ComposeResult:
         yield Grid(
             ColourChanger(),
-            Vertical(id="log"),
+            VerticalScroll(id="log"),
         )
         yield Footer()
 
diff --git a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py
index 693bde8217..0edefbefff 100644
--- a/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py
+++ b/docs/blog/snippets/2022-12-07-responsive-app-background-task/blocking02.py
@@ -1,10 +1,10 @@
 import asyncio
-from random import randint
 import time
+from random import randint
 
 from textual.app import App, ComposeResult
 from textual.color import Color
-from textual.containers import Grid, Vertical
+from textual.containers import Grid, VerticalScroll
 from textual.widget import Widget
 from textual.widgets import Footer, Label
 
@@ -29,7 +29,7 @@ class MyApp(App[None]):
     def compose(self) -> ComposeResult:
         yield Grid(
             ColourChanger(),
-            Vertical(id="log"),
+            VerticalScroll(id="log"),
         )
         yield Footer()
 
diff --git a/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py b/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py
index 2c96e5ed60..20f2daba87 100644
--- a/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py
+++ b/docs/blog/snippets/2022-12-07-responsive-app-background-task/nonblocking01.py
@@ -1,10 +1,10 @@
 import asyncio
-from random import randint
 import time
+from random import randint
 
 from textual.app import App, ComposeResult
 from textual.color import Color
-from textual.containers import Grid, Vertical
+from textual.containers import Grid, VerticalScroll
 from textual.widget import Widget
 from textual.widgets import Footer, Label
 
@@ -29,7 +29,7 @@ class MyApp(App[None]):
     def compose(self) -> ComposeResult:
         yield Grid(
             ColourChanger(),
-            Vertical(id="log"),
+            VerticalScroll(id="log"),
         )
         yield Footer()
 
diff --git a/docs/examples/events/dictionary.py b/docs/examples/events/dictionary.py
index cf85c2bf6a..51421ad392 100644
--- a/docs/examples/events/dictionary.py
+++ b/docs/examples/events/dictionary.py
@@ -6,9 +6,10 @@
     raise ImportError("Please install httpx with 'pip install httpx' ")
 
 from rich.json import JSON
+
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
-from textual.widgets import Static, Input
+from textual.containers import VerticalScroll
+from textual.widgets import Input, Static
 
 
 class DictionaryApp(App):
@@ -18,7 +19,7 @@ class DictionaryApp(App):
 
     def compose(self) -> ComposeResult:
         yield Input(placeholder="Search for a word")
-        yield Vertical(Static(id="results"), id="results-container")
+        yield VerticalScroll(Static(id="results"), id="results-container")
 
     async def on_input_changed(self, message: Input.Changed) -> None:
         """A coroutine to handle a text changed message."""
diff --git a/docs/examples/guide/layout/combining_layouts.py b/docs/examples/guide/layout/combining_layouts.py
index c52ecce0d6..e608152cd9 100644
--- a/docs/examples/guide/layout/combining_layouts.py
+++ b/docs/examples/guide/layout/combining_layouts.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Container, Horizontal, Vertical
+from textual.containers import Container, Horizontal, VerticalScroll
 from textual.widgets import Header, Static
 
 
@@ -9,7 +9,7 @@ class CombiningLayoutsExample(App):
     def compose(self) -> ComposeResult:
         yield Header()
         with Container(id="app-grid"):
-            with Vertical(id="left-pane"):
+            with VerticalScroll(id="left-pane"):
                 for number in range(15):
                     yield Static(f"Vertical layout, child {number}")
             with Horizontal(id="top-right"):
diff --git a/docs/examples/styles/height_comparison.py b/docs/examples/styles/height_comparison.py
index c679a68e6c..41d8f0a766 100644
--- a/docs/examples/styles/height_comparison.py
+++ b/docs/examples/styles/height_comparison.py
@@ -1,6 +1,6 @@
 from textual.app import App
-from textual.containers import Vertical
-from textual.widgets import Placeholder, Label, Static
+from textual.containers import VerticalScroll
+from textual.widgets import Label, Placeholder, Static
 
 
 class Ruler(Static):
@@ -11,7 +11,7 @@ def compose(self):
 
 class HeightComparisonApp(App):
     def compose(self):
-        yield Vertical(
+        yield VerticalScroll(
             Placeholder(id="cells"),  # (1)!
             Placeholder(id="percent"),
             Placeholder(id="w"),
diff --git a/docs/examples/styles/max_width.py b/docs/examples/styles/max_width.py
index 0ee440a923..7ea482dc71 100644
--- a/docs/examples/styles/max_width.py
+++ b/docs/examples/styles/max_width.py
@@ -1,11 +1,11 @@
 from textual.app import App
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Placeholder
 
 
 class MaxWidthApp(App):
     def compose(self):
-        yield Vertical(
+        yield VerticalScroll(
             Placeholder("max-width: 50h", id="p1"),
             Placeholder("max-width: 999", id="p2"),
             Placeholder("max-width: 50%", id="p3"),
diff --git a/docs/examples/styles/min_width.css b/docs/examples/styles/min_width.css
index 43dd8400ac..ba2dd81c60 100644
--- a/docs/examples/styles/min_width.css
+++ b/docs/examples/styles/min_width.css
@@ -1,4 +1,4 @@
-Vertical {
+VerticalScroll {
     height: 100%;
     width: 100%;
     overflow-x: auto;
@@ -10,7 +10,8 @@ Placeholder {
 }
 
 #p1 {
-    min-width: 25%;  /* (1)! */
+    min-width: 25%;
+    /* (1)! */
 }
 
 #p2 {
diff --git a/docs/examples/styles/min_width.py b/docs/examples/styles/min_width.py
index 25195c61d7..b008812660 100644
--- a/docs/examples/styles/min_width.py
+++ b/docs/examples/styles/min_width.py
@@ -1,11 +1,11 @@
 from textual.app import App
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Placeholder
 
 
 class MinWidthApp(App):
     def compose(self):
-        yield Vertical(
+        yield VerticalScroll(
             Placeholder("min-width: 25%", id="p1"),
             Placeholder("min-width: 75%", id="p2"),
             Placeholder("min-width: 100", id="p3"),
diff --git a/docs/examples/styles/overflow.css b/docs/examples/styles/overflow.css
index 3b68440c75..5274290978 100644
--- a/docs/examples/styles/overflow.css
+++ b/docs/examples/styles/overflow.css
@@ -3,7 +3,7 @@ Screen {
     color: black;
 }
 
-Vertical {
+VerticalScroll {
     width: 1fr;
 }
 
@@ -13,7 +13,7 @@ Static {
     border: green wide;
     color: white 90%;
     height: auto;
-}    
+}
 
 #right {
     overflow-y: hidden;
diff --git a/docs/examples/styles/overflow.py b/docs/examples/styles/overflow.py
index b9a6c33831..debe0252d3 100644
--- a/docs/examples/styles/overflow.py
+++ b/docs/examples/styles/overflow.py
@@ -1,6 +1,6 @@
 from textual.app import App
+from textual.containers import Horizontal, VerticalScroll
 from textual.widgets import Static
-from textual.containers import Horizontal, Vertical
 
 TEXT = """I must not fear.
 Fear is the mind-killer.
@@ -14,8 +14,8 @@
 class OverflowApp(App):
     def compose(self):
         yield Horizontal(
-            Vertical(Static(TEXT), Static(TEXT), Static(TEXT), id="left"),
-            Vertical(Static(TEXT), Static(TEXT), Static(TEXT), id="right"),
+            VerticalScroll(Static(TEXT), Static(TEXT), Static(TEXT), id="left"),
+            VerticalScroll(Static(TEXT), Static(TEXT), Static(TEXT), id="right"),
         )
 
 
diff --git a/docs/examples/styles/visibility_containers.py b/docs/examples/styles/visibility_containers.py
index 879b916fbc..a94de145d9 100644
--- a/docs/examples/styles/visibility_containers.py
+++ b/docs/examples/styles/visibility_containers.py
@@ -1,11 +1,11 @@
 from textual.app import App
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
 from textual.widgets import Placeholder
 
 
 class VisibilityContainersApp(App):
     def compose(self):
-        yield Vertical(
+        yield VerticalScroll(
             Horizontal(
                 Placeholder(),
                 Placeholder(),
diff --git a/docs/examples/widgets/button.css b/docs/examples/widgets/button.css
index 5f1c906da3..aee652f688 100644
--- a/docs/examples/widgets/button.css
+++ b/docs/examples/widgets/button.css
@@ -2,7 +2,7 @@ Button {
     margin: 1 2;
 }
 
-Horizontal > Vertical {
+Horizontal>VerticalScroll {
     width: 24;
 }
 
diff --git a/docs/examples/widgets/button.py b/docs/examples/widgets/button.py
index 4c3509c325..09339ccb0b 100644
--- a/docs/examples/widgets/button.py
+++ b/docs/examples/widgets/button.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
 from textual.widgets import Button, Static
 
 
@@ -8,7 +8,7 @@ class ButtonsApp(App[str]):
 
     def compose(self) -> ComposeResult:
         yield Horizontal(
-            Vertical(
+            VerticalScroll(
                 Static("Standard Buttons", classes="header"),
                 Button("Default"),
                 Button("Primary!", variant="primary"),
@@ -16,7 +16,7 @@ def compose(self) -> ComposeResult:
                 Button.warning("Warning!"),
                 Button.error("Error!"),
             ),
-            Vertical(
+            VerticalScroll(
                 Static("Disabled Buttons", classes="header"),
                 Button("Default", disabled=True),
                 Button("Primary!", variant="primary", disabled=True),
diff --git a/docs/examples/widgets/checkbox.css b/docs/examples/widgets/checkbox.css
index 4e80b66850..b6e17c093e 100644
--- a/docs/examples/widgets/checkbox.css
+++ b/docs/examples/widgets/checkbox.css
@@ -2,7 +2,7 @@ Screen {
     align: center middle;
 }
 
-Vertical {
+VerticalScroll {
     width: auto;
     height: auto;
     border: solid $primary;
diff --git a/docs/examples/widgets/checkbox.py b/docs/examples/widgets/checkbox.py
index 5ad6ca1ff5..75eadda0cf 100644
--- a/docs/examples/widgets/checkbox.py
+++ b/docs/examples/widgets/checkbox.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Checkbox
 
 
@@ -7,7 +7,7 @@ class CheckboxApp(App[None]):
     CSS_PATH = "checkbox.css"
 
     def compose(self) -> ComposeResult:
-        with Vertical():
+        with VerticalScroll():
             yield Checkbox("Arrakis :sweat:")
             yield Checkbox("Caladan")
             yield Checkbox("Chusuk")
diff --git a/docs/examples/widgets/placeholder.py b/docs/examples/widgets/placeholder.py
index c09e9c412b..9c7d6eb0e1 100644
--- a/docs/examples/widgets/placeholder.py
+++ b/docs/examples/widgets/placeholder.py
@@ -1,14 +1,13 @@
 from textual.app import App, ComposeResult
-from textual.containers import Container, Horizontal, Vertical
+from textual.containers import Container, Horizontal, VerticalScroll
 from textual.widgets import Placeholder
 
 
 class PlaceholderApp(App):
-
     CSS_PATH = "placeholder.css"
 
     def compose(self) -> ComposeResult:
-        yield Vertical(
+        yield VerticalScroll(
             Container(
                 Placeholder("This is a custom label for p1.", id="p1"),
                 Placeholder("Placeholder p2 here!", id="p2"),
diff --git a/docs/examples/widgets/radio_set_changed.css b/docs/examples/widgets/radio_set_changed.css
index 47583c1d86..fc1299294d 100644
--- a/docs/examples/widgets/radio_set_changed.css
+++ b/docs/examples/widgets/radio_set_changed.css
@@ -1,4 +1,4 @@
-Vertical {
+VerticalScroll {
     align: center middle;
 }
 
diff --git a/docs/examples/widgets/radio_set_changed.py b/docs/examples/widgets/radio_set_changed.py
index c360cb8687..c817b6e6f7 100644
--- a/docs/examples/widgets/radio_set_changed.py
+++ b/docs/examples/widgets/radio_set_changed.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
 from textual.widgets import Label, RadioButton, RadioSet
 
 
@@ -7,7 +7,7 @@ class RadioSetChangedApp(App[None]):
     CSS_PATH = "radio_set_changed.css"
 
     def compose(self) -> ComposeResult:
-        with Vertical():
+        with VerticalScroll():
             with Horizontal():
                 with RadioSet():
                     yield RadioButton("Battlestar Galactica")
diff --git a/docs/guide/layout.md b/docs/guide/layout.md
index ba142f9add..3564208a9a 100644
--- a/docs/guide/layout.md
+++ b/docs/guide/layout.md
@@ -134,8 +134,8 @@ exceeds the available horizontal space in the parent container.
 
 ## Utility containers
 
-Textual comes with several "container" widgets.
-These are [Vertical][textual.containers.Vertical], [Horizontal][textual.containers.Horizontal], and [Grid][textual.containers.Grid] which have the corresponding layout.
+Textual comes with [several "container" widgets][textual.containers].
+Among them, we have [Vertical][textual.containers.Vertical], [Horizontal][textual.containers.Horizontal], and [Grid][textual.containers.Grid] which have the corresponding layout.
 
 The example below shows how we can combine these containers to create a simple 2x2 grid.
 Inside a single `Horizontal` container, we place two `Vertical` containers.
@@ -163,8 +163,8 @@ However, Textual comes with a more powerful mechanism for achieving this known a
 
 ## Composing with context managers
 
-In the previous section we've show how you add children to a container (such as `Horizontal` and `Vertical`) using positional arguments.
-It's fine to do it this way, but Textual offers a simplified syntax using [context managers](https://docs.python.org/3/reference/datamodel.html#context-managers) which is generally easier to write and edit.
+In the previous section, we've shown how you add children to a container (such as `Horizontal` and `Vertical`) using positional arguments.
+It's fine to do it this way, but Textual offers a simplified syntax using [context managers](https://docs.python.org/3/reference/datamodel.html#context-managers), which is generally easier to write and edit.
 
 When composing a widget, you can introduce a container using Python's `with` statement.
 Any widgets yielded within that block are added as a child of the container.
@@ -202,7 +202,7 @@ Let's update the [utility containers](#utility-containers) example to use the co
     ```{.textual path="docs/examples/guide/layout/utility_containers_using_with.py"}
     ```
 
-Note how the end result is the same, but the code with context managers is a little easer to read. It is up to you which method you want to use, and you can mix context managers with positional arguments if you like!
+Note how the end result is the same, but the code with context managers is a little easier to read. It is up to you which method you want to use, and you can mix context managers with positional arguments if you like!
 
 ## Grid
 
diff --git a/docs/styles/height.md b/docs/styles/height.md
index 15d5399a34..bd3fe35121 100644
--- a/docs/styles/height.md
+++ b/docs/styles/height.md
@@ -61,8 +61,8 @@ Open the CSS file tab to see the comments that explain how each height is comput
 
     1. This sets the height to 2 lines.
     2. This sets the height to 12.5% of the space made available by the container. The container is 24 lines tall, so 12.5% of 24 is 3.
-    3. This sets the height to 5% of the width of the direct container, which is the `Vertical` container. Because it expands to fit all of the terminal, the width of the `Vertical` is 80 and 5% of 80 is 4.
-    4. This sets the height to 12.5% of the height of the direct container, which is the `Vertical` container. Because it expands to fit all of the terminal, the height of the `Vertical` is 24 and 12.5% of 24 is 3.
+    3. This sets the height to 5% of the width of the direct container, which is the `VerticalScroll` container. Because it expands to fit all of the terminal, the width of the `VerticalScroll` is 80 and 5% of 80 is 4.
+    4. This sets the height to 12.5% of the height of the direct container, which is the `VerticalScroll` container. Because it expands to fit all of the terminal, the height of the `VerticalScroll` is 24 and 12.5% of 24 is 3.
     5. This sets the height to 6.25% of the viewport width, which is 80. 6.25% of 80 is 5.
     6. This sets the height to 12.5% of the viewport height, which is 24. 12.5% of 24 is 3.
     7. This sets the height of the placeholder to be the optimal size that fits the content without scrolling.
diff --git a/docs/styles/overflow.md b/docs/styles/overflow.md
index 12edaac154..d3f0ece5fa 100644
--- a/docs/styles/overflow.md
+++ b/docs/styles/overflow.md
@@ -25,7 +25,7 @@ The default setting for containers is `overflow: auto auto`.
 
 !!! warning
 
-    Some built-in containers like `Horizontal` and `Vertical` override these defaults.
+    Some built-in containers like `Horizontal` and `VerticalScroll` override these defaults.
 
 ## Example
 
diff --git a/examples/code_browser.py b/examples/code_browser.py
index 4616be4f73..13fdf3c6dc 100644
--- a/examples/code_browser.py
+++ b/examples/code_browser.py
@@ -14,7 +14,7 @@
 
 from textual import events
 from textual.app import App, ComposeResult
-from textual.containers import Container, Vertical
+from textual.containers import Container, VerticalScroll
 from textual.reactive import var
 from textual.widgets import DirectoryTree, Footer, Header, Static
 
@@ -40,7 +40,7 @@ def compose(self) -> ComposeResult:
         yield Header()
         with Container():
             yield DirectoryTree(path, id="tree-view")
-            with Vertical(id="code-view"):
+            with VerticalScroll(id="code-view"):
                 yield Static(id="code", expand=True)
         yield Footer()
 
diff --git a/src/textual/_segment_tools.py b/src/textual/_segment_tools.py
index 5f9df333bd..79f3bc0772 100644
--- a/src/textual/_segment_tools.py
+++ b/src/textual/_segment_tools.py
@@ -192,11 +192,10 @@ def align_lines(
         style: Background style.
         size: Size of container.
         horizontal: Horizontal alignment.
-        vertical: Vertical alignment
+        vertical: Vertical alignment.
 
     Returns:
         Aligned lines.
-
     """
 
     width, height = size
diff --git a/src/textual/cli/previews/borders.py b/src/textual/cli/previews/borders.py
index a33320a0a7..09dbcaab4f 100644
--- a/src/textual/cli/previews/borders.py
+++ b/src/textual/cli/previews/borders.py
@@ -1,6 +1,6 @@
 from textual.app import App, ComposeResult
 from textual.constants import BORDERS
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Button, Label
 
 TEXT = """I must not fear.
@@ -12,7 +12,7 @@
 Where the fear has gone there will be nothing. Only I will remain."""
 
 
-class BorderButtons(Vertical):
+class BorderButtons(VerticalScroll):
     DEFAULT_CSS = """
     BorderButtons {
         dock: left;
diff --git a/src/textual/cli/previews/colors.py b/src/textual/cli/previews/colors.py
index b9d8da3eba..d8028f7c5b 100644
--- a/src/textual/cli/previews/colors.py
+++ b/src/textual/cli/previews/colors.py
@@ -1,11 +1,11 @@
 from textual.app import App, ComposeResult
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
 from textual.design import ColorSystem
 from textual.widget import Widget
 from textual.widgets import Button, Footer, Label, Static
 
 
-class ColorButtons(Vertical):
+class ColorButtons(VerticalScroll):
     def compose(self) -> ComposeResult:
         for border in ColorSystem.COLOR_NAMES:
             if border:
@@ -20,15 +20,15 @@ class ColorItem(Horizontal):
     pass
 
 
-class ColorGroup(Vertical):
+class ColorGroup(VerticalScroll):
     pass
 
 
-class Content(Vertical):
+class Content(VerticalScroll):
     pass
 
 
-class ColorsView(Vertical):
+class ColorsView(VerticalScroll):
     def compose(self) -> ComposeResult:
         LEVELS = [
             "darken-3",
diff --git a/src/textual/cli/previews/easing.py b/src/textual/cli/previews/easing.py
index 70dc45758a..7bca58efa7 100644
--- a/src/textual/cli/previews/easing.py
+++ b/src/textual/cli/previews/easing.py
@@ -5,7 +5,7 @@
 from textual._easing import EASING
 from textual.app import App, ComposeResult
 from textual.cli.previews.borders import TEXT
-from textual.containers import Container, Horizontal, Vertical
+from textual.containers import Container, Horizontal, VerticalScroll
 from textual.reactive import reactive, var
 from textual.scrollbar import ScrollBarRender
 from textual.widget import Widget
@@ -73,7 +73,7 @@ def compose(self) -> ComposeResult:
         )
 
         yield EasingButtons()
-        with Vertical():
+        with VerticalScroll():
             with Horizontal(id="inputs"):
                 yield Label("Animation Duration:", id="label")
                 yield duration_input
diff --git a/src/textual/containers.py b/src/textual/containers.py
index bbd4c13d73..fbae6ecb79 100644
--- a/src/textual/containers.py
+++ b/src/textual/containers.py
@@ -14,11 +14,23 @@ class Container(Widget):
 
 
 class Vertical(Widget):
-    """A container widget which aligns children vertically."""
+    """A container which lays children vertically."""
 
     DEFAULT_CSS = """
     Vertical {
-        height: 1fr;
+        width: 1fr;
+        layout: vertical;
+        overflow: hidden hidden;
+    }
+    """
+
+
+class VerticalScroll(Widget):
+    """A container which aligns children vertically and overflows automatically."""
+
+    DEFAULT_CSS = """
+    VerticalScroll {
+        width: 1fr;
         layout: vertical;
         overflow-y: auto;
     }
@@ -26,25 +38,61 @@ class Vertical(Widget):
 
 
 class Horizontal(Widget):
-    """A container widget which aligns children horizontally."""
+    """A container which lays children horizontally."""
 
     DEFAULT_CSS = """
     Horizontal {
         height: 1fr;
         layout: horizontal;
-        overflow-x: hidden;
+        overflow: hidden hidden;
+    }
+    """
+
+
+class HorizontalScroll(Widget):
+    """A container which lays children horizontally and overflows automatically."""
+
+    DEFAULT_CSS = """
+    HorizontalScroll {
+        height: 1fr;
+        layout: horizontal;
+        overflow-x: auto;
+    }
+    """
+
+
+class Center(Widget):
+    """A container which centers children horizontally."""
+
+    DEFAULT_CSS = """
+    Center {
+        align-horizontal: center;
+        height: auto;
+        width: 1fr;
+    }
+    """
+
+
+class Middle(Widget):
+    """A container which aligns children vertically in the middle."""
+
+    DEFAULT_CSS = """
+    Middle {
+        align-vertical: middle;
+        width: auto;
+        height: 1fr;
     }
     """
 
 
 class Grid(Widget):
-    """A container widget with grid alignment."""
+    """A container with grid alignment."""
 
     DEFAULT_CSS = """
     Grid {
         height: 1fr;
         layout: grid;
-    }    
+    }
     """
 
 
@@ -52,7 +100,7 @@ class Content(Widget, can_focus=True, can_focus_children=False):
     """A container for content such as text."""
 
     DEFAULT_CSS = """
-    Vertical {
+    VerticalScroll {
         height: 1fr;
         layout: vertical;
         overflow-y: auto;
diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py
index 013c51ead6..8f95d011d6 100644
--- a/src/textual/widgets/_list_view.py
+++ b/src/textual/widgets/_list_view.py
@@ -4,7 +4,7 @@
 
 from textual.await_remove import AwaitRemove
 from textual.binding import Binding, BindingType
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.geometry import clamp
 from textual.message import Message
 from textual.reactive import reactive
@@ -12,7 +12,7 @@
 from textual.widgets._list_item import ListItem
 
 
-class ListView(Vertical, can_focus=True, can_focus_children=False):
+class ListView(VerticalScroll, can_focus=True, can_focus_children=False):
     """A vertical list view widget.
 
     Displays a vertical list of `ListItem`s which can be highlighted and
diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py
index 8e36b3e5e0..36931fd595 100644
--- a/src/textual/widgets/_markdown.py
+++ b/src/textual/widgets/_markdown.py
@@ -10,7 +10,7 @@
 from typing_extensions import TypeAlias
 
 from ..app import ComposeResult
-from ..containers import Horizontal, Vertical
+from ..containers import Horizontal, VerticalScroll
 from ..message import Message
 from ..reactive import reactive, var
 from ..widget import Widget
@@ -266,7 +266,7 @@ class MarkdownBulletList(MarkdownList):
         width: 1fr;
     }
 
-    MarkdownBulletList Vertical {
+    MarkdownBulletList VerticalScroll {
         height: auto;
         width: 1fr;
     }
@@ -277,7 +277,7 @@ def compose(self) -> ComposeResult:
             if isinstance(block, MarkdownListItem):
                 bullet = MarkdownBullet()
                 bullet.symbol = block.bullet
-                yield Horizontal(bullet, Vertical(*block._blocks))
+                yield Horizontal(bullet, VerticalScroll(*block._blocks))
         self._blocks.clear()
 
 
@@ -295,7 +295,7 @@ class MarkdownOrderedList(MarkdownList):
         width: 1fr;
     }
 
-    MarkdownOrderedList  Vertical {
+    MarkdownOrderedList VerticalScroll {
         height: auto;
         width: 1fr;
     }
@@ -311,7 +311,7 @@ def compose(self) -> ComposeResult:
             if isinstance(block, MarkdownListItem):
                 bullet = MarkdownBullet()
                 bullet.symbol = block.bullet.rjust(symbol_size + 1)
-                yield Horizontal(bullet, Vertical(*block._blocks))
+                yield Horizontal(bullet, VerticalScroll(*block._blocks))
 
         self._blocks.clear()
 
@@ -410,7 +410,7 @@ class MarkdownListItem(MarkdownBlock):
         height: auto;
     }
 
-    MarkdownListItem > Vertical {
+    MarkdownListItem > VerticalScroll {
         width: 1fr;
         height: auto;
     }
@@ -761,7 +761,7 @@ async def on_tree_node_selected(self, message: Tree.NodeSelected) -> None:
             )
 
 
-class MarkdownViewer(Vertical, can_focus=True, can_focus_children=True):
+class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True):
     """A Markdown viewer widget."""
 
     DEFAULT_CSS = """
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index cb7b92d301..4640380ecc 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -1,3 +1,165 @@
+# name: test_alignment_containers
+  '''
+  <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
+      <!-- Generated with Rich https://www.textualize.io -->
+      <style>
+  
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Regular"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+          font-style: normal;
+          font-weight: 400;
+      }
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Bold"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+          font-style: bold;
+          font-weight: 700;
+      }
+  
+      .terminal-1593336641-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-1593336641-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-1593336641-r1 { fill: #c5c8c6 }
+  .terminal-1593336641-r2 { fill: #7ae998 }
+  .terminal-1593336641-r3 { fill: #0a180e;font-weight: bold }
+  .terminal-1593336641-r4 { fill: #008139 }
+  .terminal-1593336641-r5 { fill: #e3dbce }
+  .terminal-1593336641-r6 { fill: #e1e1e1 }
+  .terminal-1593336641-r7 { fill: #e76580 }
+  .terminal-1593336641-r8 { fill: #f5e5e9;font-weight: bold }
+  .terminal-1593336641-r9 { fill: #780028 }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-1593336641-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-1593336641-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1593336641-line-22">
+      <rect x="0" y="538.3" width="976" height="24.65"/>
+              </clipPath>
+      </defs>
+  
+      <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-1593336641-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">AlignContainersApp</text>
+              <g transform="translate(26,22)">
+              <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+              <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+              <circle cx="44" cy="0" r="7" fill="#28c840"/>
+              </g>
+          
+      <g transform="translate(9, 41)" clip-path="url(#terminal-1593336641-clip-terminal)">
+      <rect fill="#1b2127" x="0" y="1.5" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="390.4" y="1.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b2127" x="585.6" y="1.5" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b2127" x="0" y="25.9" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="390.4" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="439.2" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="451.4" y="25.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="524.6" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="536.8" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b2127" x="585.6" y="25.9" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b2127" x="0" y="50.3" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="390.4" y="50.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b2127" x="585.6" y="50.3" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="74.7" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="74.7" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="74.7" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="99.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="99.1" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="123.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="123.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="147.9" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="147.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="172.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="172.3" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="196.7" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="196.7" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="221.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="221.1" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="245.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="245.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="269.9" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="269.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="0" y="294.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="294.3" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="0" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="48.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="61" y="318.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="134.2" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="146.4" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="318.7" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="0" y="343.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="343.1" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="367.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="367.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="391.9" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="391.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="416.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="416.3" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="440.7" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="440.7" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="465.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="465.1" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="489.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="489.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="513.9" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="513.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="538.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="538.3" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#342b1f" x="0" y="562.7" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="562.7" width="780.8" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-1593336641-matrix">
+      <text class="terminal-1593336641-r2" x="390.4" y="20" textLength="195.2" clip-path="url(#terminal-1593336641-line-0)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-1593336641-r1" x="976" y="20" textLength="12.2" clip-path="url(#terminal-1593336641-line-0)">
+  </text><text class="terminal-1593336641-r3" x="451.4" y="44.4" textLength="73.2" clip-path="url(#terminal-1593336641-line-1)">center</text><text class="terminal-1593336641-r1" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-1593336641-line-1)">
+  </text><text class="terminal-1593336641-r4" x="390.4" y="68.8" textLength="195.2" clip-path="url(#terminal-1593336641-line-2)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-1593336641-r1" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-1593336641-line-2)">
+  </text><text class="terminal-1593336641-r1" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-1593336641-line-3)">
+  </text><text class="terminal-1593336641-r1" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-1593336641-line-4)">
+  </text><text class="terminal-1593336641-r1" x="976" y="142" textLength="12.2" clip-path="url(#terminal-1593336641-line-5)">
+  </text><text class="terminal-1593336641-r1" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-1593336641-line-6)">
+  </text><text class="terminal-1593336641-r1" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-1593336641-line-7)">
+  </text><text class="terminal-1593336641-r1" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-1593336641-line-8)">
+  </text><text class="terminal-1593336641-r1" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-1593336641-line-9)">
+  </text><text class="terminal-1593336641-r1" x="976" y="264" textLength="12.2" clip-path="url(#terminal-1593336641-line-10)">
+  </text><text class="terminal-1593336641-r1" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-1593336641-line-11)">
+  </text><text class="terminal-1593336641-r7" x="0" y="312.8" textLength="195.2" clip-path="url(#terminal-1593336641-line-12)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-1593336641-r1" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-1593336641-line-12)">
+  </text><text class="terminal-1593336641-r8" x="61" y="337.2" textLength="73.2" clip-path="url(#terminal-1593336641-line-13)">middle</text><text class="terminal-1593336641-r1" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-1593336641-line-13)">
+  </text><text class="terminal-1593336641-r9" x="0" y="361.6" textLength="195.2" clip-path="url(#terminal-1593336641-line-14)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-1593336641-r1" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-1593336641-line-14)">
+  </text><text class="terminal-1593336641-r1" x="976" y="386" textLength="12.2" clip-path="url(#terminal-1593336641-line-15)">
+  </text><text class="terminal-1593336641-r1" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-1593336641-line-16)">
+  </text><text class="terminal-1593336641-r1" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-1593336641-line-17)">
+  </text><text class="terminal-1593336641-r1" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-1593336641-line-18)">
+  </text><text class="terminal-1593336641-r1" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-1593336641-line-19)">
+  </text><text class="terminal-1593336641-r1" x="976" y="508" textLength="12.2" clip-path="url(#terminal-1593336641-line-20)">
+  </text><text class="terminal-1593336641-r1" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-1593336641-line-21)">
+  </text><text class="terminal-1593336641-r1" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-1593336641-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_auto_table
   '''
   <svg class="rich-terminal" viewBox="0 0 1482 1026.0" xmlns="http://www.w3.org/2000/svg">
@@ -15031,6 +15193,172 @@
   
   '''
 # ---
+# name: test_layout_containers
+  '''
+  <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
+      <!-- Generated with Rich https://www.textualize.io -->
+      <style>
+  
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Regular"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+          font-style: normal;
+          font-weight: 400;
+      }
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Bold"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+          font-style: bold;
+          font-weight: 700;
+      }
+  
+      .terminal-4197777529-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-4197777529-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-4197777529-r1 { fill: #7ae998 }
+  .terminal-4197777529-r2 { fill: #e76580 }
+  .terminal-4197777529-r3 { fill: #1e1e1e }
+  .terminal-4197777529-r4 { fill: #121212 }
+  .terminal-4197777529-r5 { fill: #c5c8c6 }
+  .terminal-4197777529-r6 { fill: #0a180e;font-weight: bold }
+  .terminal-4197777529-r7 { fill: #f5e5e9;font-weight: bold }
+  .terminal-4197777529-r8 { fill: #e2e2e2 }
+  .terminal-4197777529-r9 { fill: #008139 }
+  .terminal-4197777529-r10 { fill: #780028 }
+  .terminal-4197777529-r11 { fill: #e1e1e1 }
+  .terminal-4197777529-r12 { fill: #23568b }
+  .terminal-4197777529-r13 { fill: #14191f }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-4197777529-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-4197777529-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-4197777529-line-22">
+      <rect x="0" y="538.3" width="976" height="24.65"/>
+              </clipPath>
+      </defs>
+  
+      <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-4197777529-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">MyApp</text>
+              <g transform="translate(26,22)">
+              <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+              <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+              <circle cx="44" cy="0" r="7" fill="#28c840"/>
+              </g>
+          
+      <g transform="translate(9, 41)" clip-path="url(#terminal-4197777529-clip-terminal)">
+      <rect fill="#4ebf71" x="0" y="1.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="195.2" y="1.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="390.4" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="402.6" y="1.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="488" y="1.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="683.2" y="1.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="878.4" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="890.6" y="1.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="0" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="48.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="61" y="25.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="134.2" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="146.4" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="195.2" y="25.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="231.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="244" y="25.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="329.4" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="341.6" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="390.4" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="402.6" y="25.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="427" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="488" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="536.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="549" y="25.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="622.2" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="634.4" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="683.2" y="25.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="719.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="732" y="25.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="817.4" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="829.6" y="25.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="878.4" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="890.6" y="25.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="915" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="0" y="50.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="195.2" y="50.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="390.4" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="402.6" y="50.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="488" y="50.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="683.2" y="50.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="878.4" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="890.6" y="50.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="74.7" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="99.1" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="123.5" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="147.9" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="172.3" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="196.7" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="221.1" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="245.5" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="488" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="488" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="500.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="512.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="524.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="536.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="549" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="561.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="573.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="585.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="597.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="610" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="622.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="634.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="646.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="658.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="671" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="683.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="695.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="707.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="719.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="732" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="744.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="756.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="768.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="780.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="793" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="805.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="817.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="829.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="841.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="854" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="866.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="878.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="890.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="902.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="915" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="927.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="939.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="963.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="0" y="294.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="294.3" width="292.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="488" y="294.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="683.2" y="294.3" width="268.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="294.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="0" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="48.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="61" y="318.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="134.2" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="146.4" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="318.7" width="292.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="488" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="536.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="549" y="318.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="622.2" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="634.4" y="318.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="683.2" y="318.7" width="268.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="318.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="0" y="343.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="343.1" width="292.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#4ebf71" x="488" y="343.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="683.2" y="343.1" width="268.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="343.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="0" y="367.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="367.5" width="292.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="488" y="367.5" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="683.2" y="367.5" width="268.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="367.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="0" y="391.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="36.6" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="48.8" y="391.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="134.2" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="146.4" y="391.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="391.9" width="292.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="488" y="391.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="524.6" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="536.8" y="391.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="622.2" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="634.4" y="391.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="683.2" y="391.9" width="268.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="391.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="0" y="416.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="416.3" width="292.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#b93c5b" x="488" y="416.3" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="683.2" y="416.3" width="268.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#23568b" x="951.6" y="416.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="12.2" y="440.7" width="475.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="488" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="500.2" y="440.7" width="451.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="440.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="12.2" y="465.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="36.6" y="465.1" width="451.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="488" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="500.2" y="465.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="524.6" y="465.1" width="427" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="465.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="12.2" y="489.5" width="475.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="488" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#262626" x="500.2" y="489.5" width="451.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="489.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="513.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="85.4" y="513.9" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="500.2" y="513.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="573.4" y="513.9" width="378.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="513.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="85.4" y="538.3" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="538.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="573.4" y="538.3" width="378.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="538.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="85.4" y="562.7" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="562.7" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="573.4" y="562.7" width="378.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#14191f" x="951.6" y="562.7" width="24.4" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-4197777529-matrix">
+      <text class="terminal-4197777529-r1" x="0" y="20" textLength="195.2" clip-path="url(#terminal-4197777529-line-0)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r2" x="195.2" y="20" textLength="195.2" clip-path="url(#terminal-4197777529-line-0)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r3" x="390.4" y="20" textLength="12.2" clip-path="url(#terminal-4197777529-line-0)">▊</text><text class="terminal-4197777529-r4" x="402.6" y="20" textLength="85.4" clip-path="url(#terminal-4197777529-line-0)">▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r1" x="488" y="20" textLength="195.2" clip-path="url(#terminal-4197777529-line-0)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r2" x="683.2" y="20" textLength="195.2" clip-path="url(#terminal-4197777529-line-0)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r3" x="878.4" y="20" textLength="12.2" clip-path="url(#terminal-4197777529-line-0)">▊</text><text class="terminal-4197777529-r4" x="890.6" y="20" textLength="85.4" clip-path="url(#terminal-4197777529-line-0)">▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r5" x="976" y="20" textLength="12.2" clip-path="url(#terminal-4197777529-line-0)">
+  </text><text class="terminal-4197777529-r6" x="61" y="44.4" textLength="73.2" clip-path="url(#terminal-4197777529-line-1)">Accept</text><text class="terminal-4197777529-r7" x="244" y="44.4" textLength="85.4" clip-path="url(#terminal-4197777529-line-1)">Decline</text><text class="terminal-4197777529-r3" x="390.4" y="44.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-1)">▊</text><text class="terminal-4197777529-r6" x="549" y="44.4" textLength="73.2" clip-path="url(#terminal-4197777529-line-1)">Accept</text><text class="terminal-4197777529-r7" x="732" y="44.4" textLength="85.4" clip-path="url(#terminal-4197777529-line-1)">Decline</text><text class="terminal-4197777529-r3" x="878.4" y="44.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-1)">▊</text><text class="terminal-4197777529-r5" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-1)">
+  </text><text class="terminal-4197777529-r9" x="0" y="68.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-2)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r10" x="195.2" y="68.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-2)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r3" x="390.4" y="68.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-2)">▊</text><text class="terminal-4197777529-r4" x="402.6" y="68.8" textLength="85.4" clip-path="url(#terminal-4197777529-line-2)">▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r9" x="488" y="68.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-2)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r10" x="683.2" y="68.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-2)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r3" x="878.4" y="68.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-2)">▊</text><text class="terminal-4197777529-r4" x="890.6" y="68.8" textLength="85.4" clip-path="url(#terminal-4197777529-line-2)">▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r5" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-2)">
+  </text><text class="terminal-4197777529-r5" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-4197777529-line-3)">
+  </text><text class="terminal-4197777529-r5" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-4197777529-line-4)">
+  </text><text class="terminal-4197777529-r5" x="976" y="142" textLength="12.2" clip-path="url(#terminal-4197777529-line-5)">
+  </text><text class="terminal-4197777529-r5" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-6)">
+  </text><text class="terminal-4197777529-r5" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-7)">
+  </text><text class="terminal-4197777529-r5" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-4197777529-line-8)">
+  </text><text class="terminal-4197777529-r5" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-4197777529-line-9)">
+  </text><text class="terminal-4197777529-r5" x="976" y="264" textLength="12.2" clip-path="url(#terminal-4197777529-line-10)">
+  </text><text class="terminal-4197777529-r12" x="646.6" y="288.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-11)">▌</text><text class="terminal-4197777529-r5" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-11)">
+  </text><text class="terminal-4197777529-r1" x="0" y="312.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-12)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r1" x="488" y="312.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-12)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r5" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-12)">
+  </text><text class="terminal-4197777529-r6" x="61" y="337.2" textLength="73.2" clip-path="url(#terminal-4197777529-line-13)">Accept</text><text class="terminal-4197777529-r6" x="549" y="337.2" textLength="73.2" clip-path="url(#terminal-4197777529-line-13)">Accept</text><text class="terminal-4197777529-r5" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-4197777529-line-13)">
+  </text><text class="terminal-4197777529-r9" x="0" y="361.6" textLength="195.2" clip-path="url(#terminal-4197777529-line-14)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r9" x="488" y="361.6" textLength="195.2" clip-path="url(#terminal-4197777529-line-14)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r5" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-4197777529-line-14)">
+  </text><text class="terminal-4197777529-r2" x="0" y="386" textLength="195.2" clip-path="url(#terminal-4197777529-line-15)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r2" x="488" y="386" textLength="195.2" clip-path="url(#terminal-4197777529-line-15)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r5" x="976" y="386" textLength="12.2" clip-path="url(#terminal-4197777529-line-15)">
+  </text><text class="terminal-4197777529-r7" x="48.8" y="410.4" textLength="85.4" clip-path="url(#terminal-4197777529-line-16)">Decline</text><text class="terminal-4197777529-r7" x="536.8" y="410.4" textLength="85.4" clip-path="url(#terminal-4197777529-line-16)">Decline</text><text class="terminal-4197777529-r5" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-16)">
+  </text><text class="terminal-4197777529-r10" x="0" y="434.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-17)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r10" x="488" y="434.8" textLength="195.2" clip-path="url(#terminal-4197777529-line-17)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r13" x="951.6" y="434.8" textLength="24.4" clip-path="url(#terminal-4197777529-line-17)">▆▆</text><text class="terminal-4197777529-r5" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-17)">
+  </text><text class="terminal-4197777529-r3" x="0" y="459.2" textLength="12.2" clip-path="url(#terminal-4197777529-line-18)">▊</text><text class="terminal-4197777529-r4" x="12.2" y="459.2" textLength="475.8" clip-path="url(#terminal-4197777529-line-18)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r3" x="488" y="459.2" textLength="12.2" clip-path="url(#terminal-4197777529-line-18)">▊</text><text class="terminal-4197777529-r4" x="500.2" y="459.2" textLength="451.4" clip-path="url(#terminal-4197777529-line-18)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-4197777529-r5" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-4197777529-line-18)">
+  </text><text class="terminal-4197777529-r3" x="0" y="483.6" textLength="12.2" clip-path="url(#terminal-4197777529-line-19)">▊</text><text class="terminal-4197777529-r3" x="488" y="483.6" textLength="12.2" clip-path="url(#terminal-4197777529-line-19)">▊</text><text class="terminal-4197777529-r5" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-4197777529-line-19)">
+  </text><text class="terminal-4197777529-r3" x="0" y="508" textLength="12.2" clip-path="url(#terminal-4197777529-line-20)">▊</text><text class="terminal-4197777529-r4" x="12.2" y="508" textLength="475.8" clip-path="url(#terminal-4197777529-line-20)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r3" x="488" y="508" textLength="12.2" clip-path="url(#terminal-4197777529-line-20)">▊</text><text class="terminal-4197777529-r4" x="500.2" y="508" textLength="451.4" clip-path="url(#terminal-4197777529-line-20)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-4197777529-r5" x="976" y="508" textLength="12.2" clip-path="url(#terminal-4197777529-line-20)">
+  </text><text class="terminal-4197777529-r11" x="0" y="532.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-21)">0</text><text class="terminal-4197777529-r11" x="488" y="532.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-21)">0</text><text class="terminal-4197777529-r5" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-4197777529-line-21)">
+  </text><text class="terminal-4197777529-r5" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-4197777529-line-22)">
+  </text><text class="terminal-4197777529-r11" x="0" y="581.2" textLength="85.4" clip-path="url(#terminal-4197777529-line-23)">1000000</text><text class="terminal-4197777529-r11" x="488" y="581.2" textLength="85.4" clip-path="url(#terminal-4197777529-line-23)">1000000</text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_line_api_scrollbars
   '''
   <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
diff --git a/tests/snapshot_tests/snapshot_apps/alignment_containers.py b/tests/snapshot_tests/snapshot_apps/alignment_containers.py
new file mode 100644
index 0000000000..c419f29e9d
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/alignment_containers.py
@@ -0,0 +1,29 @@
+"""
+App to test alignment containers.
+"""
+
+from textual.app import App, ComposeResult
+from textual.containers import Center, Middle
+from textual.widgets import Button
+
+
+class AlignContainersApp(App[None]):
+    CSS = """
+    Center {
+        tint: $primary 10%;
+    }
+    Middle {
+        tint: $secondary 10%;
+    }
+    """
+
+    def compose(self) -> ComposeResult:
+        with Center():
+            yield Button.success("center")
+        with Middle():
+            yield Button.error("middle")
+
+
+app = AlignContainersApp()
+if __name__ == "__main__":
+    app.run()
diff --git a/tests/snapshot_tests/snapshot_apps/auto_width_input.py b/tests/snapshot_tests/snapshot_apps/auto_width_input.py
index 35cc3b2ec2..e312e653ed 100644
--- a/tests/snapshot_tests/snapshot_apps/auto_width_input.py
+++ b/tests/snapshot_tests/snapshot_apps/auto_width_input.py
@@ -1,10 +1,9 @@
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Header, Footer, Label, Input
 
 
 class InputWidthAutoApp(App[None]):
-
     CSS = """
     Input.auto {
         width: auto;
diff --git a/tests/snapshot_tests/snapshot_apps/disable_widgets.py b/tests/snapshot_tests/snapshot_apps/disable_widgets.py
index 8965fbdac4..fb321b3b60 100644
--- a/tests/snapshot_tests/snapshot_apps/disable_widgets.py
+++ b/tests/snapshot_tests/snapshot_apps/disable_widgets.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Vertical, Horizontal
+from textual.containers import VerticalScroll, Horizontal
 from textual.widgets import (
     Header,
     Footer,
@@ -53,7 +53,7 @@ def test_tree(self) -> Tree:
 
     def compose(self) -> ComposeResult:
         yield Header()
-        yield Vertical(
+        yield VerticalScroll(
             Horizontal(
                 Button(),
                 Button(variant="primary"),
@@ -76,7 +76,7 @@ def compose(self) -> ComposeResult:
 
     def on_mount(self) -> None:
         self.query_one(TextLog).write("Hello, World!")
-        self.query_one("#test-container", Vertical).disabled = True
+        self.query_one("#test-container", VerticalScroll).disabled = True
 
 
 if __name__ == "__main__":
diff --git a/tests/snapshot_tests/snapshot_apps/focus_component_class.py b/tests/snapshot_tests/snapshot_apps/focus_component_class.py
index 85becd79b5..77ac46212c 100644
--- a/tests/snapshot_tests/snapshot_apps/focus_component_class.py
+++ b/tests/snapshot_tests/snapshot_apps/focus_component_class.py
@@ -1,7 +1,7 @@
 from rich.text import Text
 
 from textual.app import App, ComposeResult, RenderResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Header, Footer
 from textual.widget import Widget
 
@@ -32,7 +32,7 @@ def render(self) -> RenderResult:
 class StyleBugApp(App[None]):
     def compose(self) -> ComposeResult:
         yield Header()
-        with Vertical():
+        with VerticalScroll():
             for n in range(40):
                 yield Tester(n)
         yield Footer()
diff --git a/tests/snapshot_tests/snapshot_apps/fr_units.py b/tests/snapshot_tests/snapshot_apps/fr_units.py
index d9bcddeb94..1ece79ebc8 100644
--- a/tests/snapshot_tests/snapshot_apps/fr_units.py
+++ b/tests/snapshot_tests/snapshot_apps/fr_units.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Horizontal, Vertical
+from textual.containers import Horizontal, VerticalScroll
 from textual.widgets import Static
 
 
@@ -8,7 +8,6 @@ class StaticText(Static):
 
 
 class FRApp(App):
-
     CSS = """
     StaticText {
         height: 1fr;
@@ -39,7 +38,7 @@ class FRApp(App):
     """
 
     def compose(self) -> ComposeResult:
-        yield Vertical(
+        yield VerticalScroll(
             StaticText("HEADER", id="header"),
             Horizontal(
                 StaticText("foo", id="foo"),
diff --git a/tests/snapshot_tests/snapshot_apps/layer_order_independence.py b/tests/snapshot_tests/snapshot_apps/layer_order_independence.py
index 2ad43e931a..a3deec8bae 100644
--- a/tests/snapshot_tests/snapshot_apps/layer_order_independence.py
+++ b/tests/snapshot_tests/snapshot_apps/layer_order_independence.py
@@ -1,7 +1,7 @@
 from textual.app import App, ComposeResult
 from textual.screen import Screen
 from textual.widgets import Header, Footer, Label
-from textual.containers import Vertical, Container
+from textual.containers import VerticalScroll, Container
 
 
 class Overlay(Container):
@@ -9,12 +9,12 @@ def compose(self) -> ComposeResult:
         yield Label("This should float over the top")
 
 
-class Body1(Vertical):
+class Body1(VerticalScroll):
     def compose(self) -> ComposeResult:
         yield Label("My God! It's full of stars! " * 300)
 
 
-class Body2(Vertical):
+class Body2(VerticalScroll):
     def compose(self) -> ComposeResult:
         yield Label("My God! It's full of stars! " * 300)
 
@@ -36,7 +36,6 @@ def compose(self) -> ComposeResult:
 
 
 class Layers(App[None]):
-
     CSS = """
     Screen {
         layers: base higher;
diff --git a/tests/snapshot_tests/snapshot_apps/layout_containers.py b/tests/snapshot_tests/snapshot_apps/layout_containers.py
new file mode 100644
index 0000000000..c06b6270c4
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/layout_containers.py
@@ -0,0 +1,56 @@
+"""
+App to test layout containers.
+"""
+
+from typing import Iterable
+
+from textual.app import App, ComposeResult
+from textual.containers import (
+    Grid,
+    Horizontal,
+    HorizontalScroll,
+    Vertical,
+    VerticalScroll,
+)
+from textual.widget import Widget
+from textual.widgets import Button, Input, Label
+
+
+def sub_compose() -> Iterable[Widget]:
+    yield Button.success("Accept")
+    yield Button.error("Decline")
+    yield Input()
+    yield Label("\n\n".join([str(n * 1_000_000) for n in range(10)]))
+
+
+class MyApp(App[None]):
+    CSS = """
+    Grid {
+        grid-size: 2 2;
+        grid-rows: 1fr;
+        grid-columns: 1fr;
+    }
+    Grid > Widget {
+        width: 100%;
+        height: 100%;
+    }
+    Input {
+        width: 80;
+    }
+    """
+
+    def compose(self) -> ComposeResult:
+        with Grid():
+            with Horizontal():
+                yield from sub_compose()
+            with HorizontalScroll():
+                yield from sub_compose()
+            with Vertical():
+                yield from sub_compose()
+            with VerticalScroll():
+                yield from sub_compose()
+
+
+app = MyApp()
+if __name__ == "__main__":
+    app.run()
diff --git a/tests/snapshot_tests/snapshot_apps/line_api_scrollbars.py b/tests/snapshot_tests/snapshot_apps/line_api_scrollbars.py
index 68a16a0991..26ab2c9854 100644
--- a/tests/snapshot_tests/snapshot_apps/line_api_scrollbars.py
+++ b/tests/snapshot_tests/snapshot_apps/line_api_scrollbars.py
@@ -1,7 +1,7 @@
 from rich.text import Text
 
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widget import Widget
 from textual.widgets import TextLog
 
@@ -21,32 +21,32 @@ class ScrollViewApp(App):
     Screen {
         align: center middle;
     }
-    
+
     TextLog {
         width:13;
-        height:10;        
+        height:10;
     }
 
-    Vertical{
+    VerticalScroll {
         width:13;
         height: 10;
         overflow: scroll;
         overflow-x: auto;
     }
+
     MyWidget {
         width:13;
         height:auto;
     }
-
     """
 
     def compose(self) -> ComposeResult:
         yield TextLog()
-        yield Vertical(MyWidget())
+        yield VerticalScroll(MyWidget())
 
     def on_ready(self) -> None:
         self.query_one(TextLog).write("\n".join(f"{n} 0123456789" for n in range(20)))
-        self.query_one(Vertical).scroll_end(animate=False)
+        self.query_one(VerticalScroll).scroll_end(animate=False)
 
 
 if __name__ == "__main__":
diff --git a/tests/snapshot_tests/snapshot_apps/nested_auto_heights.py b/tests/snapshot_tests/snapshot_apps/nested_auto_heights.py
index 5df6c2960c..4ed40f10ed 100644
--- a/tests/snapshot_tests/snapshot_apps/nested_auto_heights.py
+++ b/tests/snapshot_tests/snapshot_apps/nested_auto_heights.py
@@ -3,7 +3,7 @@
 from __future__ import annotations
 
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Static
 
 
@@ -42,8 +42,8 @@ class NestedAutoApp(App[None]):
 
     def compose(self) -> ComposeResult:
         self._static = Static("", id="my-static")
-        yield Vertical(
-            Vertical(
+        yield VerticalScroll(
+            VerticalScroll(
                 self._static,
                 id="my-static-wrapper",
             ),
diff --git a/tests/snapshot_tests/snapshot_apps/offsets.py b/tests/snapshot_tests/snapshot_apps/offsets.py
index 12099b1262..23d21bb386 100644
--- a/tests/snapshot_tests/snapshot_apps/offsets.py
+++ b/tests/snapshot_tests/snapshot_apps/offsets.py
@@ -1,5 +1,5 @@
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Label, Static
 
 
@@ -18,7 +18,6 @@ def compose(self) -> ComposeResult:
 
 
 class OffsetsApp(App):
-
     CSS = """
     
     #box1 {
diff --git a/tests/snapshot_tests/snapshot_apps/visibility.py b/tests/snapshot_tests/snapshot_apps/visibility.py
index a5ccccb7e2..7dbfcf7b43 100644
--- a/tests/snapshot_tests/snapshot_apps/visibility.py
+++ b/tests/snapshot_tests/snapshot_apps/visibility.py
@@ -1,5 +1,5 @@
 from textual.app import App
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import Static
 
 
@@ -10,7 +10,7 @@ class Visibility(App):
     Screen {
         layout: horizontal;        
     }
-    Vertical {
+    VerticalScroll {
         width: 1fr;
         border: solid red;
     }
@@ -30,13 +30,12 @@ class Visibility(App):
     """
 
     def compose(self):
-
-        yield Vertical(
+        yield VerticalScroll(
             Static("foo"),
             Static("float", classes="float"),
             id="container1",
         )
-        yield Vertical(
+        yield VerticalScroll(
             Static("bar"),
             Static("float", classes="float"),
             id="container2",
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
index 0b26c83c95..4e2882eab4 100644
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -48,6 +48,14 @@ def test_dock_layout_sidebar(snap_compare):
     assert snap_compare(LAYOUT_EXAMPLES_DIR / "dock_layout2_sidebar.py")
 
 
+def test_layout_containers(snap_compare):
+    assert snap_compare(SNAPSHOT_APPS_DIR / "layout_containers.py")
+
+
+def test_alignment_containers(snap_compare):
+    assert snap_compare(SNAPSHOT_APPS_DIR / "alignment_containers.py")
+
+
 # --- Widgets - rendering and basic interactions ---
 # Each widget should have a canonical example that is display in the docs.
 # When adding a new widget, ideally we should also create a snapshot test
diff --git a/tests/test_containers.py b/tests/test_containers.py
new file mode 100644
index 0000000000..22a4ad0ec7
--- /dev/null
+++ b/tests/test_containers.py
@@ -0,0 +1,100 @@
+"""Test basic functioning of some containers."""
+
+from textual.app import App, ComposeResult
+from textual.containers import (
+    Center,
+    Horizontal,
+    HorizontalScroll,
+    Middle,
+    Vertical,
+    VerticalScroll,
+)
+from textual.widgets import Label
+
+
+async def test_horizontal_vs_horizontalscroll_scrolling():
+    """Check the default scrollbar behaviours for `Horizontal` and `HorizontalScroll`."""
+
+    class HorizontalsApp(App[None]):
+        CSS = """
+        Screen {
+            layout: vertical;
+        }
+        """
+
+        def compose(self) -> ComposeResult:
+            with Horizontal():
+                for _ in range(10):
+                    yield Label("How is life going? " * 3 + " | ")
+            with HorizontalScroll():
+                for _ in range(10):
+                    yield Label("How is life going? " * 3 + " | ")
+
+    WIDTH = 80
+    HEIGHT = 24
+    app = HorizontalsApp()
+    async with app.run_test(size=(WIDTH, HEIGHT)):
+        horizontal = app.query_one(Horizontal)
+        horizontal_scroll = app.query_one(HorizontalScroll)
+        assert horizontal.size.height == horizontal_scroll.size.height
+        assert horizontal.scrollbars_enabled == (False, False)
+        assert horizontal_scroll.scrollbars_enabled == (False, True)
+
+
+async def test_vertical_vs_verticalscroll_scrolling():
+    """Check the default scrollbar behaviours for `Vertical` and `VerticalScroll`."""
+
+    class VerticalsApp(App[None]):
+        CSS = """
+        Screen {
+            layout: horizontal;
+        }
+        """
+
+        def compose(self) -> ComposeResult:
+            with Vertical():
+                for _ in range(10):
+                    yield Label("How is life going?\n" * 3 + "\n\n")
+            with VerticalScroll():
+                for _ in range(10):
+                    yield Label("How is life going?\n" * 3 + "\n\n")
+
+    WIDTH = 80
+    HEIGHT = 24
+    app = VerticalsApp()
+    async with app.run_test(size=(WIDTH, HEIGHT)):
+        vertical = app.query_one(Vertical)
+        vertical_scroll = app.query_one(VerticalScroll)
+        assert vertical.size.width == vertical_scroll.size.width
+        assert vertical.scrollbars_enabled == (False, False)
+        assert vertical_scroll.scrollbars_enabled == (True, False)
+
+
+async def test_center_container():
+    """Check the size of the container `Center`."""
+
+    class CenterApp(App[None]):
+        def compose(self) -> ComposeResult:
+            with Center():
+                yield Label("<>\n<>\n<>")
+
+    app = CenterApp()
+    async with app.run_test():
+        center = app.query_one(Center)
+        assert center.size.width == app.size.width
+        assert center.size.height == 3
+
+
+async def test_middle_container():
+    """Check the size of the container `Middle`."""
+
+    class MiddleApp(App[None]):
+        def compose(self) -> ComposeResult:
+            with Middle():
+                yield Label("1234")
+
+    app = MiddleApp()
+    async with app.run_test():
+        middle = app.query_one(Middle)
+        assert middle.size.width == 4
+        assert middle.size.height == app.size.height
diff --git a/tests/test_disabled.py b/tests/test_disabled.py
index 850fcf7c7b..bc0692ad05 100644
--- a/tests/test_disabled.py
+++ b/tests/test_disabled.py
@@ -1,7 +1,7 @@
 """Test Widget.disabled."""
 
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widgets import (
     Button,
     DataTable,
@@ -21,7 +21,7 @@ class DisableApp(App[None]):
 
     def compose(self) -> ComposeResult:
         """Compose the child widgets."""
-        yield Vertical(
+        yield VerticalScroll(
             Button(),
             DataTable(),
             DirectoryTree("."),
@@ -56,7 +56,7 @@ async def test_enabled_widgets_have_enabled_pseudo_class() -> None:
 async def test_all_individually_disabled() -> None:
     """Post-disable all widgets should report being disabled."""
     async with DisableApp().run_test() as pilot:
-        for node in pilot.app.screen.query("Vertical > *"):
+        for node in pilot.app.screen.query("VerticalScroll > *"):
             node.disabled = True
         assert all(
             node.disabled for node in pilot.app.screen.query("#test-container > *")
@@ -77,7 +77,7 @@ async def test_disabled_widgets_have_disabled_pseudo_class() -> None:
 async def test_disable_via_container() -> None:
     """All child widgets should appear (to CSS) as disabled by a container being disabled."""
     async with DisableApp().run_test() as pilot:
-        pilot.app.screen.query_one("#test-container", Vertical).disabled = True
+        pilot.app.screen.query_one("#test-container", VerticalScroll).disabled = True
         assert all(
             node.has_pseudo_class("disabled") and not node.has_pseudo_class("enabled")
             for node in pilot.app.screen.query("#test-container > *")
diff --git a/tests/test_focus.py b/tests/test_focus.py
index b4d9ce8c57..a03b9b53cd 100644
--- a/tests/test_focus.py
+++ b/tests/test_focus.py
@@ -151,11 +151,11 @@ def test_focus_next_and_previous_with_type_selector_without_self():
 
     screen = app.screen
 
-    from textual.containers import Horizontal, Vertical
+    from textual.containers import Horizontal, VerticalScroll
     from textual.widgets import Button, Input, Switch
 
     screen._add_children(
-        Vertical(
+        VerticalScroll(
             Horizontal(
                 Input(id="w3"),
                 Switch(id="w4"),
diff --git a/tests/test_overflow_change.py b/tests/test_overflow_change.py
index 236a15f8c3..7f5d7eb46e 100644
--- a/tests/test_overflow_change.py
+++ b/tests/test_overflow_change.py
@@ -1,19 +1,18 @@
 """Regression test for #1616 https://github.com/Textualize/textual/issues/1616"""
-import pytest
 
 from textual.app import App
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 
 
 async def test_overflow_change_updates_virtual_size_appropriately():
     class MyApp(App):
         def compose(self):
-            yield Vertical()
+            yield VerticalScroll()
 
     app = MyApp()
 
     async with app.run_test() as pilot:
-        vertical = app.query_one(Vertical)
+        vertical = app.query_one(VerticalScroll)
 
         height = vertical.virtual_size.height
 
diff --git a/tests/test_visibility_change.py b/tests/test_visibility_change.py
index f79f18a6fd..7006827ee2 100644
--- a/tests/test_visibility_change.py
+++ b/tests/test_visibility_change.py
@@ -1,7 +1,7 @@
 """See https://github.com/Textualize/textual/issues/1355 as the motivation for these tests."""
 
 from textual.app import App, ComposeResult
-from textual.containers import Vertical
+from textual.containers import VerticalScroll
 from textual.widget import Widget
 
 
@@ -18,7 +18,7 @@ class VisibleTester(App[None]):
     """
 
     def compose(self) -> ComposeResult:
-        yield Vertical(
+        yield VerticalScroll(
             Widget(id="keep"), Widget(id="hide-via-code"), Widget(id="hide-via-css")
         )
 


Code review comments:
diff_hunk: @@ -14,45 +14,93 @@ class Container(Widget):
 
 
 class Vertical(Widget):
-    """A container widget which aligns children vertically."""
+    """A container which lays children vertically."""
path: src/textual/containers.py
text: @user1:
I don't think "lays" is quite the right way to phrase this here; `aligns` seemed to make more says, or "lays out" would work.

@author:
I don't like "align" because that can be confused with the `align-*` styles. The children are not aligned vertically (e.g., one might be to the left and the other might be to the right).

What if I write "A container which composes children vertically."?
I don't love this wording either, but I think it is better than using "align".

@user2:
I don't think `composes` works, as it might confuse with our compose method.

How about `arranges`? "A container which arranges children vertically."

@author:
I like "arranges".

diff_hunk: @@ -14,45 +14,93 @@ class Container(Widget):
 
 
 class Vertical(Widget):
-    """A container widget which aligns children vertically."""
+    """A container which lays children vertically."""
 
     DEFAULT_CSS = """
     Vertical {
-        height: 1fr;
+        width: 1fr;
+        layout: vertical;
+        overflow: hidden hidden;
+    }
+    """
+
+
+class VerticalScroll(Widget):
+    """A container which aligns children vertically and overflows automatically."""
+
+    DEFAULT_CSS = """
+    VerticalScroll {
+        width: 1fr;
         layout: vertical;
         overflow-y: auto;
     }
     """
 
 
 class Horizontal(Widget):
-    """A container widget which aligns children horizontally."""
+    """A container which lays children horizontally."""
 
     DEFAULT_CSS = """
     Horizontal {
         height: 1fr;
         layout: horizontal;
-        overflow-x: hidden;
+        overflow: hidden hidden;
+    }
+    """
+
+
+class HorizontalScroll(Widget):
+    """A container which lays children horizontally and overflows automatically."""
path: src/textual/containers.py
text: @user1:
Similar comment to earlier for "lays" here.