Repository: Textualize/textual
Pull request: Implement border (sub)title.
This will close #1864.

Problem statement:
Add title to CSS borders
We should add the ability to render a title (displayed in the top) and a subtitle (displayed in the bottom) to CSS borders. Something akin to Rich panels.

Widget should grow `border_title` and `border_subtitle` attributes, which should accept a str or Text in the setter, and return a Text in the getter.

Additionally, there should be new CSS declaration s:

`border-title-align: <left, center, right>`
`border-subtitle-align: <left, center, right>`

Note that it only makes sense to render a single line in the titles. So we need a little logic to get the first line.

If the line doesn't fit, it should be truncated with ellipsis. The Text object can do this (set no_wrap=True, and overflow="ellipsis")

See panel.py in Rich for inspiration.

Hints from issue discussion:
Yeah, this would be great. In one of my projects, I apply border to a container and use `Label` widget to display a title before the container. Having a `Panel` like experience would be better.

Will there be a subtitle option as well?
@learnbyexample Yeah, don't see why not.
I have a couple of follow-up questions regarding the specs:

 - Suppose the border is blank, none, or hidden:
   - Do we display non-empty titles and subtitles?
   - What if the title or subtitle is empty?
   - What if the title or subtitle is `None`?
 - What is the text colour? The same as the corresponding border edge or the text colour of the widget?

Commit message: Update CHANGELOG.md

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2a362f93b..d536b7ca2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - 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
+- Added `Widget.border_title` and `Widget.border_subtitle` to set border (sub)title for a widget https://github.com/Textualize/textual/issues/1864
+- Added CSS styles `border_title_align` and `border_subtitle_align`.
 - Added `TabbedContent` widget https://github.com/Textualize/textual/pull/2059
 - Added `get_child_by_type` method to widgets / app https://github.com/Textualize/textual/pull/2059
 - Added `Widget.render_str` method https://github.com/Textualize/textual/pull/2059
diff --git a/docs/examples/styles/border_sub_title_align_all.css b/docs/examples/styles/border_sub_title_align_all.css
new file mode 100644
index 0000000000..973a50a0fb
--- /dev/null
+++ b/docs/examples/styles/border_sub_title_align_all.css
@@ -0,0 +1,64 @@
+Grid {
+    grid-size: 3 3;
+    align: center middle;
+}
+
+Container {
+    width: 100%;
+    height: 100%;
+    align: center middle;
+}
+
+#lbl1 {  /* (1)! */
+    border: vkey $secondary;
+}
+
+#lbl2 {  /* (2)! */
+    border: round $secondary;
+    border-title-align: right;
+    border-subtitle-align: right;
+}
+
+#lbl3 {
+    border: wide $secondary;
+    border-title-align: center;
+    border-subtitle-align: center;
+}
+
+#lbl4 {
+    border: ascii $success;
+    border-title-align: center;  /* (3)! */
+    border-subtitle-align: left;
+}
+
+#lbl5 {  /* (4)! */
+    /* No border = no (sub)title. */
+    border: none $success;
+    border-title-align: center;
+    border-subtitle-align: center;
+}
+
+#lbl6 {  /* (5)! */
+    border-top: solid $success;
+    border-bottom: solid $success;
+}
+
+#lbl7 {  /* (6)! */
+    border-top: solid $error;
+    border-bottom: solid $error;
+    padding: 1 2;
+    border-subtitle-align: left;
+}
+
+#lbl8 {
+    border-top: solid $error;
+    border-bottom: solid $error;
+    border-title-align: center;
+    border-subtitle-align: center;
+}
+
+#lbl9 {
+    border-top: solid $error;
+    border-bottom: solid $error;
+    border-title-align: right;
+}
diff --git a/docs/examples/styles/border_sub_title_align_all.py b/docs/examples/styles/border_sub_title_align_all.py
new file mode 100644
index 0000000000..a46cf1fca6
--- /dev/null
+++ b/docs/examples/styles/border_sub_title_align_all.py
@@ -0,0 +1,74 @@
+from textual.app import App
+from textual.containers import Container, Grid
+from textual.widgets import Label
+
+
+def make_label_container(  # (11)!
+    text: str, id: str, border_title: str, border_subtitle: str
+) -> Container:
+    lbl = Label(text, id=id)
+    lbl.border_title = border_title
+    lbl.border_subtitle = border_subtitle
+    return Container(lbl)
+
+
+class BorderSubTitleAlignAll(App[None]):
+    def compose(self):
+        with Grid():
+            yield make_label_container(  # (1)!
+                "This is the story of",
+                "lbl1",
+                "[b]Border [i]title[/i][/]",
+                "[u][r]Border[/r] subtitle[/]",
+            )
+            yield make_label_container(  # (2)!
+                "a Python",
+                "lbl2",
+                "[b red]Left, but it's loooooooooooong",
+                "[reverse]Center, but it's loooooooooooong",
+            )
+            yield make_label_container(  # (3)!
+                "developer that",
+                "lbl3",
+                "[b i on purple]Left[/]",
+                "[r u white on black]@@@[/]",
+            )
+            yield make_label_container(
+                "had to fill up",
+                "lbl4",
+                "",  # (4)!
+                "[link=https://textual.textualize.io]Left[/]",  # (5)!
+            )
+            yield make_label_container(
+                "nine labels", "lbl5", "Title", "Subtitle"
+            )  # (6)!
+            yield make_label_container(  # (7)!
+                "and ended up redoing it",
+                "lbl6",
+                "Title",
+                "Subtitle",
+            )
+            yield make_label_container(  # (8)!
+                "because the first try",
+                "lbl7",
+                "Title, but really loooooooooong!",
+                "Subtitle, but really loooooooooong!",
+            )
+            yield make_label_container(  # (9)!
+                "had some labels",
+                "lbl8",
+                "Title, but really loooooooooong!",
+                "Subtitle, but really loooooooooong!",
+            )
+            yield make_label_container(  # (10)!
+                "that were too long.",
+                "lbl9",
+                "Title, but really loooooooooong!",
+                "Subtitle, but really loooooooooong!",
+            )
+
+
+app = BorderSubTitleAlignAll(css_path="border_sub_title_align_all.css")
+
+if __name__ == "__main__":
+    app.run()
diff --git a/docs/examples/styles/border_subtitle_align.css b/docs/examples/styles/border_subtitle_align.css
new file mode 100644
index 0000000000..334e4eb1db
--- /dev/null
+++ b/docs/examples/styles/border_subtitle_align.css
@@ -0,0 +1,23 @@
+#label1 {
+    border: solid $secondary;
+    border-subtitle-align: left;
+}
+
+#label2 {
+    border: dashed $secondary;
+    border-subtitle-align: center;
+}
+
+#label3 {
+    border: tall $secondary;
+    border-subtitle-align: right;
+}
+
+Screen > Label {
+    width: 100%;
+    height: 5;
+    content-align: center middle;
+    color: white;
+    margin: 1;
+    box-sizing: border-box;
+}
diff --git a/docs/examples/styles/border_subtitle_align.py b/docs/examples/styles/border_subtitle_align.py
new file mode 100644
index 0000000000..4eedc0e5e9
--- /dev/null
+++ b/docs/examples/styles/border_subtitle_align.py
@@ -0,0 +1,20 @@
+from textual.app import App
+from textual.widgets import Label
+
+
+class BorderSubtitleAlignApp(App):
+    def compose(self):
+        lbl = Label("My border is on the left.", id="label1")
+        lbl.border_subtitle = "< Left"
+        yield lbl
+
+        lbl = Label("My border is centered", id="label2")
+        lbl.border_subtitle = "Centered!"
+        yield lbl
+
+        lbl = Label("My border is on the right", id="label3")
+        lbl.border_subtitle = "Right >"
+        yield lbl
+
+
+app = BorderSubtitleAlignApp(css_path="border_subtitle_align.css")
diff --git a/docs/examples/styles/border_title_align.css b/docs/examples/styles/border_title_align.css
new file mode 100644
index 0000000000..dd6b830893
--- /dev/null
+++ b/docs/examples/styles/border_title_align.css
@@ -0,0 +1,23 @@
+#label1 {
+    border: solid $secondary;
+    border-title-align: left;
+}
+
+#label2 {
+    border: dashed $secondary;
+    border-title-align: center;
+}
+
+#label3 {
+    border: tall $secondary;
+    border-title-align: right;
+}
+
+Screen > Label {
+    width: 100%;
+    height: 5;
+    content-align: center middle;
+    color: white;
+    margin: 1;
+    box-sizing: border-box;
+}
diff --git a/docs/examples/styles/border_title_align.py b/docs/examples/styles/border_title_align.py
new file mode 100644
index 0000000000..7491f3e15f
--- /dev/null
+++ b/docs/examples/styles/border_title_align.py
@@ -0,0 +1,20 @@
+from textual.app import App
+from textual.widgets import Label
+
+
+class BorderTitleAlignApp(App):
+    def compose(self):
+        lbl = Label("My border is on the left.", id="label1")
+        lbl.border_title = "< Left"
+        yield lbl
+
+        lbl = Label("My border is centered", id="label2")
+        lbl.border_title = "Centered!"
+        yield lbl
+
+        lbl = Label("My border is on the right", id="label3")
+        lbl.border_title = "Right >"
+        yield lbl
+
+
+app = BorderTitleAlignApp(css_path="border_title_align.css")
diff --git a/docs/guide/styles.md b/docs/guide/styles.md
index 4362286380..a5c1e79b08 100644
--- a/docs/guide/styles.md
+++ b/docs/guide/styles.md
@@ -257,6 +257,22 @@ There are many other border types. Run the following from the command prompt to
 textual borders
 ```
 
+
+#### Border labels (title and subtitle)
+
+A widget that has a border set can have a title in the top edge of the border.
+For that, set the attribute [`border_title`][textual.widget.Widget.border_title] in your widget.
+Similarly, a widget with a border can have a subtitle in its bottom edge.
+To change it, assign to the attribute [`border_subtitle`][textual.widget.Widget.border_subtitle].
+
+These labels must be one-line strings, but they can contain rich markup.
+Additionally, the labels can be aligned horizontally with the respective styles:
+
+ - [`border-title-align`](../styles/border_title_align.md) for the title; and
+ - [`border-subtitle-align`](../styles/border_subtitle_align.md) for the subtitle.
+
+--8<-- "docs/snippets/border_sub_title_align_all_example.md"
+
 ### Outline
 
 [Outline](../styles/outline.md) is similar to border and is set in the same way. The difference is that outline will not change the size of the widget, and may overlap the content area. The following example sets an outline on a widget:
diff --git a/docs/snippets/border_sub_title_align_all_example.md b/docs/snippets/border_sub_title_align_all_example.md
new file mode 100644
index 0000000000..7c65bdea82
--- /dev/null
+++ b/docs/snippets/border_sub_title_align_all_example.md
@@ -0,0 +1,38 @@
+This example shows all border title and subtitle alignments, together with some examples of how (sub)titles can have custom markup.
+Open the code tabs to see the details of the code examples.
+
+=== "Output"
+
+    ```{.textual path="docs/examples/styles/border_sub_title_align_all.py"}
+    ```
+
+=== "border_sub_title_align_all.py"
+
+    ```py hl_lines=""
+    --8<-- "docs/examples/styles/border_sub_title_align_all.py"
+    ```
+
+    1. Border (sub)titles can contain nested markup.
+    2. Long (sub)titles get truncated and occupy as much space as possible.
+    3. (Sub)titles can be stylised with Rich markup.
+    4. An empty (sub)title isn't displayed.
+    5. The markup can even contain Rich links.
+    6. If the widget does not have a border, the title and subtitle are not shown.
+    7. When the side borders are not set, the (sub)title will align with the edge of the widget.
+    8. The title and subtitle are aligned on the left and very long, so they get truncated and we can still see the rightmost character of the border edge.
+    9. The title and subtitle are centered and very long, so they get truncated and are centered with one character of padding on each side.
+    10. The title and subtitle are aligned on the right and very long, so they get truncated and we can still see the leftmost character of the border edge.
+    11. An auxiliary function to create labels with border title and subtitle.
+
+=== "border_sub_title_align_all.css"
+
+    ```sass hl_lines=""
+    --8<-- "docs/examples/styles/border_sub_title_align_all.css"
+    ```
+
+    1. The default alignment for the title is `left` and the default alignment for the subtitle is `right`.
+    2. Specifying an alignment when the (sub)title is too long has no effect. (Although, it will have an effect if the (sub)title is shortened or if the widget is widened.)
+    3. Setting the alignment does not affect empty (sub)titles.
+    4. If the border is not set, or set to `none`/`hidden`, the (sub)title is not shown.
+    5. If the (sub)title alignment is on a side which does not have a border edge, the (sub)title will be flush to that side.
+    6. Naturally, (sub)title positioning is affected by padding.
diff --git a/docs/styles/_template.md b/docs/styles/_template.md
index 325fb34328..e83bd55a0b 100644
--- a/docs/styles/_template.md
+++ b/docs/styles/_template.md
@@ -19,23 +19,7 @@ rule-name: <a href="../../css_types/type_one">&lt;type-one&gt;</a>;
 ### Values
 
 <!--
-If this rule only needs one type, include it directly:
-
---8<-- "docs/snippets/type_syntax/only_type.md"
--->
-
-<!--
-If this rule needs two or more types:
-
-### &lt;first-type&gt;
-
---8<-- "docs/snippets/type_syntax/first_type.md"
-
-### &lt;second-type&gt;
-
---8<-- "docs/snippets/type_syntax/second_type.md"
-
-...
+For enum-like styles that don't warrant a dedicated type.
 -->
 
 ### Defaults
@@ -120,12 +104,12 @@ Copy the same examples as the ones shown in the CSS above.
 If the programmatic way of setting the rule differs significantly from the CSS way, make note of that here.
 
 ```py
-rule_name = value1
-rule_name = value2
-rule_name = (different_syntax_value, shown_here)
+widget.styles.rule_name = value1
+widget.styles.rule_name = value2
+widget.styles.rule_name = (different_syntax_value, shown_here)
 
-rule_name_variant = value3
-rule_name_variant = value4
+widget.styles.rule_name_variant = value3
+widget.styles.rule_name_variant = value4
 ```
 
 -->
diff --git a/docs/styles/border_subtitle_align.md b/docs/styles/border_subtitle_align.md
new file mode 100644
index 0000000000..3aeb7c9749
--- /dev/null
+++ b/docs/styles/border_subtitle_align.md
@@ -0,0 +1,62 @@
+# Border-subtitle-align
+
+The `border-subtitle-align` sets the horizontal alignment for the border subtitle.
+
+## Syntax
+
+--8<-- "docs/snippets/syntax_block_start.md"
+border-subtitle-align: <a href="../../css_types/horizontal">&lt;horizontal&gt;</a>;
+--8<-- "docs/snippets/syntax_block_end.md"
+
+The style `border-subtitle-align` takes a [`<horizontal>`](../../css_types/horizontal) that determines where the border subtitle is aligned along the top edge of the border.
+This means that the border corners are always visible.
+
+### Default
+
+The default alignment is `right`.
+
+
+## Examples
+
+### Basic usage
+
+This example shows three labels, each with a different border subtitle alignment:
+
+=== "Output"
+
+    ```{.textual path="docs/examples/styles/border_subtitle_align.py"}
+    ```
+
+=== "border_subtitle_align.py"
+
+    ```py
+    --8<-- "docs/examples/styles/border_subtitle_align.py"
+    ```
+
+=== "border_subtitle_align.css"
+
+    ```sass
+    --8<-- "docs/examples/styles/border_subtitle_align.css"
+    ```
+
+
+### All title and subtitle combinations
+
+--8<-- "docs/snippets/border_sub_title_align_all_example.md"
+
+
+## CSS
+
+```sass
+border-subtitle-align: left;
+border-subtitle-align: center;
+border-subtitle-align: right;
+```
+
+## Python
+
+```py
+widget.styles.border_subtitle_align = "left"
+widget.styles.border_subtitle_align = "center"
+widget.styles.border_subtitle_align = "right"
+```
diff --git a/docs/styles/border_title_align.md b/docs/styles/border_title_align.md
new file mode 100644
index 0000000000..8a7608d63c
--- /dev/null
+++ b/docs/styles/border_title_align.md
@@ -0,0 +1,62 @@
+# Border-title-align
+
+The `border-title-align` sets the horizontal alignment for the border title.
+
+## Syntax
+
+--8<-- "docs/snippets/syntax_block_start.md"
+border-title-align: <a href="../../css_types/horizontal">&lt;horizontal&gt;</a>;
+--8<-- "docs/snippets/syntax_block_end.md"
+
+The style `border-title-align` takes a [`<horizontal>`](../../css_types/horizontal) that determines where the border title is aligned along the top edge of the border.
+This means that the border corners are always visible.
+
+### Default
+
+The default alignment is `left`.
+
+
+## Examples
+
+### Basic usage
+
+This example shows three labels, each with a different border title alignment:
+
+=== "Output"
+
+    ```{.textual path="docs/examples/styles/border_title_align.py"}
+    ```
+
+=== "border_title_align.py"
+
+    ```py
+    --8<-- "docs/examples/styles/border_title_align.py"
+    ```
+
+=== "border_title_align.css"
+
+    ```sass
+    --8<-- "docs/examples/styles/border_title_align.css"
+    ```
+
+
+### All title and subtitle combinations
+
+--8<-- "docs/snippets/border_sub_title_align_all_example.md"
+
+
+## CSS
+
+```sass
+border-title-align: left;
+border-title-align: center;
+border-title-align: right;
+```
+
+## Python
+
+```py
+widget.styles.border_title_align = "left"
+widget.styles.border_title_align = "center"
+widget.styles.border_title_align = "right"
+```
diff --git a/mkdocs-nav.yml b/mkdocs-nav.yml
index 704bd75e2a..c508c36197 100644
--- a/mkdocs-nav.yml
+++ b/mkdocs-nav.yml
@@ -67,6 +67,8 @@ nav:
       - "styles/align.md"
       - "styles/background.md"
       - "styles/border.md"
+      - "styles/border_subtitle_align.md"
+      - "styles/border_title_align.md"
       - "styles/box_sizing.md"
       - "styles/color.md"
       - "styles/content_align.md"
diff --git a/src/textual/_border.py b/src/textual/_border.py
index 1a0c10ea08..7d6dc49acb 100644
--- a/src/textual/_border.py
+++ b/src/textual/_border.py
@@ -1,13 +1,15 @@
 from __future__ import annotations
 
 from functools import lru_cache
-from typing import TYPE_CHECKING, Tuple, Union, cast
+from typing import TYPE_CHECKING, Iterable, Tuple, cast
 
+from rich.console import Console
 from rich.segment import Segment
 from rich.style import Style
+from rich.text import Text
 
 from .color import Color
-from .css.types import EdgeStyle, EdgeType
+from .css.types import AlignHorizontal, EdgeStyle, EdgeType
 
 if TYPE_CHECKING:
     from typing_extensions import TypeAlias
@@ -15,6 +17,8 @@
 INNER = 1
 OUTER = 2
 
+_EMPTY_SEGMENT = Segment("", Style())
+
 BORDER_CHARS: dict[
     EdgeType, tuple[tuple[str, str, str], tuple[str, str, str], tuple[str, str, str]]
 ] = {
@@ -103,7 +107,8 @@
 }
 
 # Some of the borders are on the widget background and some are on the background of the parent
-# This table selects which for each character, 0 indicates the widget, 1 selects the parent
+# This table selects which for each character, 0 indicates the widget, 1 selects the parent.
+# 2 and 3 reverse a cross-combination of the background and foreground colors of 0 and 1.
 BORDER_LOCATIONS: dict[
     EdgeType, tuple[tuple[int, int, int], tuple[int, int, int], tuple[int, int, int]]
 ] = {
@@ -189,6 +194,14 @@
     ),
 }
 
+# In a similar fashion, we extract the border _label_ locations for easier access when
+# rendering a border label.
+# The values are a pair with (title location, subtitle location).
+BORDER_LABEL_LOCATIONS: dict[EdgeType, tuple[int, int]] = {
+    edge_type: (locations[0][1], locations[2][1])
+    for edge_type, locations in BORDER_LOCATIONS.items()
+}
+
 INVISIBLE_EDGE_TYPES = cast("frozenset[EdgeType]", frozenset(("", "none", "hidden")))
 
 BorderValue: TypeAlias = Tuple[EdgeType, Color]
@@ -261,29 +274,170 @@ def get_box(
     )
 
 
-def render_row(
-    box_row: tuple[Segment, Segment, Segment], width: int, left: bool, right: bool
-) -> list[Segment]:
-    """Render a top, or bottom border row.
+def render_border_label(
+    label: str,
+    is_title: bool,
+    name: EdgeType,
+    width: int,
+    inner_style: Style,
+    outer_style: Style,
+    style: Style,
+    console: Console,
+    has_left_corner: bool,
+    has_right_corner: bool,
+) -> Iterable[Segment]:
+    """Render a border label (the title or subtitle) with optional markup.
+
+    The styling that may be embedded in the label will be reapplied after taking into
+    account the inner, outer, and border-specific, styles.
+
+    Args:
+        label: The label to display (that may contain markup).
+        is_title: Whether we are rendering the title (`True`) or the subtitle (`False`).
+        name: Name of the box type.
+        width: The width, in cells, of the space available for the whole edge.
+            This is the total space that may also be needed for the border corners and
+            the whitespace padding around the (sub)title. Thus, the effective space
+            available for the border label is:
+            - `width` if no corner is needed;
+            - `width - 2` if one corner is needed; and
+            - `width - 4` if both corners are needed.
+        inner_style: The inner style (widget background).
+        outer_style: The outer style (parent background).
+        style: Widget style.
+        console: The console that will render the markup in the label.
+        has_left_corner: Whether the border edge will have to render a left corner.
+        has_right_corner: Whether the border edge will have to render a right corner.
+
+    Returns:
+        A list of segments that represent the full label and surrounding padding.
+    """
+    # How many cells do we need to reserve for surrounding blanks and corners?
+    corners_needed = has_left_corner + has_right_corner
+    cells_reserved = 2 * corners_needed
+    if not label or width <= cells_reserved:
+        yield from ()
+        return
+
+    text_label = Text.from_markup(label)
+    text_label.truncate(width - cells_reserved, overflow="ellipsis")
+    segments = text_label.render(console)
+
+    label_style_location = BORDER_LABEL_LOCATIONS[name][0 if is_title else 1]
+
+    inner = inner_style + style
+    outer = outer_style + style
+
+    base_style: Style
+    if label_style_location == 0:
+        base_style = inner
+    elif label_style_location == 1:
+        base_style = outer
+    elif label_style_location == 2:
+        base_style = Style.from_color(outer.bgcolor, inner.color)
+    elif label_style_location == 3:
+        base_style = Style.from_color(inner.bgcolor, outer.color)
+    else:
+        assert False
+
+    styled_segments = [
+        Segment(segment.text, base_style + segment.style) for segment in segments
+    ]
+    blank = Segment(" ", base_style)
+    if has_left_corner:
+        yield blank
+    yield from styled_segments
+    if has_right_corner:
+        yield blank
+
+
+def _compose_row_with_label(
+    box_row: tuple[Segment, Segment, Segment],
+    width: int,
+    left: bool,
+    right: bool,
+    label_segments: Iterable[Segment],
+    label_alignment: AlignHorizontal,
+) -> Iterable[Segment]:
+    """Compose a box row with its padded label.
+
+    This is the function that actually does the work that `render_row` is intended
+    to do, but we have many lists of segments flowing around, so it becomes easier
+    to yield the segments bit by bit, and the aggregate everything into a list later.
 
     Args:
         box_row: Corners and side segments.
         width: Total width of resulting line.
         left: Render left corner.
         right: Render right corner.
+        label_segments: The segments that make up the label.
+        label_alignment: Where to horizontally align the label.
 
     Returns:
-        A list of segments.
+        An iterable of segments.
     """
     box1, box2, box3 = box_row
-    if left and right:
-        return [box1, Segment(box2.text * (width - 2), box2.style), box3]
+
+    corners_needed = left + right
+    label_segments_list = list(label_segments)
+
+    label_length = sum((segment.cell_length for segment in label_segments_list), 0)
+    space_available = max(0, width - corners_needed - label_length)
+
     if left:
-        return [box1, Segment(box2.text * (width - 1), box2.style)]
-    if right:
-        return [Segment(box2.text * (width - 1), box2.style), box3]
+        yield box1
+
+    if not space_available:
+        yield from label_segments_list
+    elif not label_length:
+        yield Segment(box2.text * space_available, box2.style)
+    elif label_alignment == "left" or label_alignment == "right":
+        edge = Segment(box2.text * space_available, box2.style)
+        if label_alignment == "left":
+            yield from label_segments_list
+            yield edge
+        else:
+            yield edge
+            yield from label_segments_list
+    elif label_alignment == "center":
+        length_on_left = space_available // 2
+        length_on_right = space_available - length_on_left
+        yield Segment(box2.text * length_on_left, box2.style)
+        yield from label_segments_list
+        yield Segment(box2.text * length_on_right, box2.style)
     else:
-        return [Segment(box2.text * width, box2.style)]
+        assert False
+
+    if right:
+        yield box3
+
+
+def render_row(
+    box_row: tuple[Segment, Segment, Segment],
+    width: int,
+    left: bool,
+    right: bool,
+    label_segments: Iterable[Segment],
+    label_alignment: AlignHorizontal = "left",
+) -> list[Segment]:
+    """Render a top or bottom border row.
+
+    Args:
+        box_row: Corners and side segments.
+        width: Total width of resulting line.
+        left: Render left corner.
+        right: Render right corner.
+        label_segments: The segments that make up the label.
+        label_alignments: Where to horizontally align the label.
+
+    Returns:
+        A list of segments.
+    """
+    return list(
+        _compose_row_with_label(
+            box_row, width, left, right, label_segments, label_alignment
+        )
+    )
 
 
 _edge_type_normalization_table: dict[EdgeType, EdgeType] = {
diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py
index 3cb4aebeb9..14f90c6036 100644
--- a/src/textual/_styles_cache.py
+++ b/src/textual/_styles_cache.py
@@ -4,10 +4,11 @@
 from sys import intern
 from typing import TYPE_CHECKING, Callable, Iterable
 
+from rich.console import Console
 from rich.segment import Segment
 from rich.style import Style
 
-from ._border import get_box, render_row
+from ._border import get_box, render_border_label, render_row
 from ._opacity import _apply_opacity
 from ._segment_tools import line_pad, line_trim
 from .color import Color
@@ -113,6 +114,9 @@ def render_widget(self, widget: Widget, crop: Region) -> list[Strip]:
             base_background,
             background,
             widget.render_line,
+            widget.app.console,
+            widget.border_title,
+            widget.border_subtitle,
             content_size=widget.content_region.size,
             padding=styles.padding,
             crop=crop,
@@ -141,6 +145,9 @@ def render(
         base_background: Color,
         background: Color,
         render_content_line: RenderLineCallback,
+        console: Console,
+        border_title: str,
+        border_subtitle: str,
         content_size: Size | None = None,
         padding: Spacing | None = None,
         crop: Region | None = None,
@@ -154,9 +161,13 @@ def render(
             base_background: Background color beneath widget.
             background: Background color of widget.
             render_content_line: Callback to render content line.
-            content_size: Size of content or None to assume full size. Defaults to None.
-            padding: Override padding from Styles, or None to use styles.padding. Defaults to None.
-            crop: Region to crop to. Defaults to None.
+            console: The console in use by the app.
+            border_title: The title for the widget border.
+            border_subtitle: The subtitle for the widget border.
+            content_size: Size of content or None to assume full size.
+            padding: Override padding from Styles, or None to use styles.padding.
+            crop: Region to crop to.
+            filter: Additional post-processing for the segments.
 
         Returns:
             Rendered lines.
@@ -188,6 +199,9 @@ def render(
                     base_background,
                     background,
                     render_content_line,
+                    console,
+                    border_title,
+                    border_subtitle,
                 )
                 self._cache[y] = strip
             else:
@@ -213,6 +227,9 @@ def render_line(
         base_background: Color,
         background: Color,
         render_content_line: Callable[[int], Strip],
+        console: Console,
+        border_title: str,
+        border_subtitle: str,
     ) -> Strip:
         """Render a styled line.
 
@@ -225,6 +242,9 @@ def render_line(
             base_background: Background color of widget beneath this line.
             background: Background color of widget.
             render_content_line: Callback to render a line of content.
+            console: The console in use by the app.
+            border_title: The title for the widget border.
+            border_subtitle: The subtitle for the widget border.
 
         Returns:
             A line of segments.
@@ -275,20 +295,42 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]:
         line: Iterable[Segment]
         # Draw top or bottom borders (A)
         if (border_top and y == 0) or (border_bottom and y == height - 1):
+            is_top = y == 0
             border_color = base_background + (
-                border_top_color if y == 0 else border_bottom_color
+                border_top_color if is_top else border_bottom_color
+            )
+            border_color_as_style = from_color(color=border_color.rich_color)
+            border_edge_type = border_top if is_top else border_bottom
+            has_left = border_left != ""
+            has_right = border_right != ""
+            label_segments = render_border_label(
+                border_title if is_top else border_subtitle,
+                is_top,
+                border_edge_type,
+                width,
+                inner,
+                outer,
+                border_color_as_style,
+                console,
+                has_left,
+                has_right,
             )
             box_segments = get_box(
-                border_top if y == 0 else border_bottom,
+                border_edge_type,
                 inner,
                 outer,
-                from_color(color=border_color.rich_color),
+                border_color_as_style,
+            )
+            label_alignment = (
+                styles.border_title_align if is_top else styles.border_subtitle_align
             )
             line = render_row(
-                box_segments[0 if y == 0 else 2],
+                box_segments[0 if is_top else 2],
                 width,
-                border_left != "",
-                border_right != "",
+                has_left,
+                has_right,
+                label_segments,
+                label_alignment,
             )
 
         # Draw padding (B)
@@ -353,6 +395,7 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]:
                 width,
                 outline_left != "",
                 outline_right != "",
+                (),
             )
 
         elif outline_left or outline_right:
diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py
index b98cd8c040..ef98fb77e8 100644
--- a/src/textual/css/_styles_builder.py
+++ b/src/textual/css/_styles_builder.py
@@ -812,6 +812,9 @@ def process_align_vertical(self, name: str, tokens: list[Token]) -> None:
     process_content_align_horizontal = process_align_horizontal
     process_content_align_vertical = process_align_vertical
 
+    process_border_title_align = process_align_horizontal
+    process_border_subtitle_align = process_align_horizontal
+
     def process_scrollbar_gutter(self, name: str, tokens: list[Token]) -> None:
         try:
             value = self._process_enum(name, tokens, VALID_SCROLLBAR_GUTTER)
diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py
index a9687f9232..087cc3211e 100644
--- a/src/textual/css/styles.py
+++ b/src/textual/css/styles.py
@@ -96,6 +96,9 @@ class RulesMap(TypedDict, total=False):
     border_bottom: tuple[str, Color]
     border_left: tuple[str, Color]
 
+    border_title_align: AlignHorizontal
+    border_subtitle_align: AlignHorizontal
+
     outline_top: tuple[str, Color]
     outline_right: tuple[str, Color]
     outline_bottom: tuple[str, Color]
@@ -235,6 +238,9 @@ class StylesBase(ABC):
     border_bottom = BoxProperty(Color(0, 255, 0))
     border_left = BoxProperty(Color(0, 255, 0))
 
+    border_title_align = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left")
+    border_subtitle_align = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "right")
+
     outline = BorderProperty(layout=False)
     outline_top = BoxProperty(Color(0, 255, 0))
     outline_right = BoxProperty(Color(0, 255, 0))
@@ -918,6 +924,11 @@ def append_declaration(name: str, value: str) -> None:
         if "text_align" in rules:
             append_declaration("text-align", self.text_align)
 
+        if "border_title_align" in rules:
+            append_declaration("border-title-align", self.border_title_align)
+        if "border_subtitle_align" in rules:
+            append_declaration("border-subtitle-align", self.border_subtitle_align)
+
         if "opacity" in rules:
             append_declaration("opacity", str(self.opacity))
         if "text_opacity" in rules:
diff --git a/src/textual/widget.py b/src/textual/widget.py
index 837206ad18..c2295038c9 100644
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -232,6 +232,10 @@ class Widget(DOMNode):
     """Widget will highlight links automatically."""
     disabled = Reactive(False)
     """The disabled state of the widget. `True` if disabled, `False` if not."""
+    border_title = Reactive("")
+    """The one-line border title, which may contain markup to be parsed."""
+    border_subtitle = Reactive("")
+    """The one-line border subtitle, which may contain markup to be parsed."""
 
     hover_style: Reactive[Style] = Reactive(Style, repaint=False)
     highlight_link_id: Reactive[str] = Reactive("")
@@ -2390,6 +2394,20 @@ def watch_disabled(self) -> None:
         """Update the styles of the widget and its children when disabled is toggled."""
         self._update_styles()
 
+    def validate_border_title(self, title: str) -> str:
+        """Ensure we only use a single line for the border title."""
+        if not title:
+            return title
+        first, *_ = title.splitlines()
+        return first
+
+    def validate_border_subtitle(self, subtitle: str) -> str:
+        """Ensure we only use a single line for the border subtitle."""
+        if not subtitle:
+            return subtitle
+        first, *_ = subtitle.splitlines()
+        return first
+
     def _size_updated(
         self, size: Size, virtual_size: Size, container_size: Size, layout: bool = True
     ) -> bool:
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index 326be7e82d..a9eb2d6c26 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -2936,6 +2936,487 @@
   
   '''
 # ---
+# name: test_css_property[border_sub_title_align_all.py]
+  '''
+  <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-3789115238-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-3789115238-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-3789115238-r1 { fill: #e1e1e1 }
+  .terminal-3789115238-r2 { fill: #c5c8c6 }
+  .terminal-3789115238-r3 { fill: #fea62b }
+  .terminal-3789115238-r4 { fill: #fea62b;font-weight: bold }
+  .terminal-3789115238-r5 { fill: #fea62b;font-weight: bold;font-style: italic; }
+  .terminal-3789115238-r6 { fill: #cc555a;font-weight: bold }
+  .terminal-3789115238-r7 { fill: #1e1e1e }
+  .terminal-3789115238-r8 { fill: #1e1e1e;text-decoration: underline; }
+  .terminal-3789115238-r9 { fill: #fea62b;text-decoration: underline; }
+  .terminal-3789115238-r10 { fill: #4b4e55;text-decoration: underline; }
+  .terminal-3789115238-r11 { fill: #4ebf71 }
+  .terminal-3789115238-r12 { fill: #b93c5b }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-3789115238-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-3789115238-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-3789115238-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-3789115238-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">BorderSubTitleAlignAll</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-3789115238-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="1.5" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="1.5" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="1.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="1.5" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="1.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="25.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="25.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="36.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="48.8" y="50.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="134.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="207.4" y="50.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="292.8" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="50.3" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="427" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="439.2" y="50.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="512.4" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="524.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="536.8" y="50.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="707.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="50.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="768.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#af00ff" x="780.8" y="50.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="829.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="841.8" y="50.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="890.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="902.8" y="50.3" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="36.6" y="74.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="292.8" y="74.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="74.7" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="427" y="74.7" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="524.6" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="536.8" y="74.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="74.7" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="707.6" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="74.7" width="170.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="890.6" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="902.8" y="74.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="36.6" y="99.1" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="85.4" y="99.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="158.6" y="99.1" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="268.4" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="292.8" y="99.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="99.1" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="427" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="439.2" y="99.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="512.4" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="524.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="536.8" y="99.1" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="99.1" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="707.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="719.8" y="99.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="768.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#c5c8c6" x="780.8" y="99.1" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="817.4" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="829.6" y="99.1" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="890.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="902.8" y="99.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="123.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="123.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="147.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="147.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="172.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="172.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="196.7" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="196.7" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="196.7" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="196.7" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="221.1" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="221.1" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="245.5" width="170.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="244" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="256.2" y="245.5" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="245.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="671" y="245.5" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="732" y="245.5" width="219.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="269.9" width="170.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="244" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="256.2" y="269.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="269.9" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="549" y="269.9" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="269.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="671" y="269.9" width="280.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="269.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="85.4" y="294.3" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="134.2" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="146.4" y="294.3" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="244" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="256.2" y="294.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="294.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="294.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="671" y="294.3" width="183" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="854" y="294.3" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="294.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="318.7" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="318.7" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="343.1" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="343.1" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="367.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="367.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="391.9" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="391.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="391.9" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="391.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="305" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="416.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="416.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="305" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="440.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="390.4" y="440.7" width="183" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="573.4" y="440.7" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="440.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="695.4" y="440.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="440.7" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="465.1" width="256.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="465.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="305" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="465.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="390.4" y="465.1" width="183" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="573.4" y="465.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="465.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="695.4" y="465.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="465.1" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="305" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="489.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="390.4" y="489.5" width="183" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="573.4" y="489.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="489.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="695.4" y="489.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="489.5" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="305" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="513.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="513.9" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="538.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="538.3" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="562.7" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="646.6" y="562.7" width="329.4" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-3789115238-matrix">
+      <text class="terminal-3789115238-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-3789115238-line-0)">
+  </text><text class="terminal-3789115238-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-1)">
+  </text><text class="terminal-3789115238-r3" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">▏</text><text class="terminal-3789115238-r4" x="48.8" y="68.8" textLength="85.4" clip-path="url(#terminal-3789115238-line-2)">Border&#160;</text><text class="terminal-3789115238-r5" x="134.2" y="68.8" textLength="61" clip-path="url(#terminal-3789115238-line-2)">title</text><text class="terminal-3789115238-r3" x="280.6" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">▕</text><text class="terminal-3789115238-r3" x="414.8" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">╭</text><text class="terminal-3789115238-r6" x="439.2" y="68.8" textLength="73.2" clip-path="url(#terminal-3789115238-line-2)">Left,…</text><text class="terminal-3789115238-r3" x="524.6" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">╮</text><text class="terminal-3789115238-r3" x="707.6" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">▁</text><text class="terminal-3789115238-r3" x="719.8" y="68.8" textLength="48.8" clip-path="url(#terminal-3789115238-line-2)">▁▁▁▁</text><text class="terminal-3789115238-r5" x="780.8" y="68.8" textLength="48.8" clip-path="url(#terminal-3789115238-line-2)">Left</text><text class="terminal-3789115238-r3" x="841.8" y="68.8" textLength="48.8" clip-path="url(#terminal-3789115238-line-2)">▁▁▁▁</text><text class="terminal-3789115238-r3" x="890.6" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">▁</text><text class="terminal-3789115238-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-2)">
+  </text><text class="terminal-3789115238-r3" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">▏</text><text class="terminal-3789115238-r1" x="36.6" y="93.2" textLength="244" clip-path="url(#terminal-3789115238-line-3)">This&#160;is&#160;the&#160;story&#160;of</text><text class="terminal-3789115238-r3" x="280.6" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">▕</text><text class="terminal-3789115238-r3" x="414.8" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">│</text><text class="terminal-3789115238-r1" x="427" y="93.2" textLength="97.6" clip-path="url(#terminal-3789115238-line-3)">a&#160;Python</text><text class="terminal-3789115238-r3" x="524.6" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">│</text><text class="terminal-3789115238-r3" x="707.6" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">▎</text><text class="terminal-3789115238-r1" x="719.8" y="93.2" textLength="170.8" clip-path="url(#terminal-3789115238-line-3)">developer&#160;that</text><text class="terminal-3789115238-r7" x="890.6" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">▋</text><text class="terminal-3789115238-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-3)">
+  </text><text class="terminal-3789115238-r3" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">▏</text><text class="terminal-3789115238-r8" x="85.4" y="117.6" textLength="73.2" clip-path="url(#terminal-3789115238-line-4)">Border</text><text class="terminal-3789115238-r9" x="158.6" y="117.6" textLength="109.8" clip-path="url(#terminal-3789115238-line-4)">&#160;subtitle</text><text class="terminal-3789115238-r3" x="280.6" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">▕</text><text class="terminal-3789115238-r3" x="414.8" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">╰</text><text class="terminal-3789115238-r7" x="439.2" y="117.6" textLength="73.2" clip-path="url(#terminal-3789115238-line-4)">Cente…</text><text class="terminal-3789115238-r3" x="524.6" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">╯</text><text class="terminal-3789115238-r3" x="707.6" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">▔</text><text class="terminal-3789115238-r3" x="719.8" y="117.6" textLength="48.8" clip-path="url(#terminal-3789115238-line-4)">▔▔▔▔</text><text class="terminal-3789115238-r10" x="780.8" y="117.6" textLength="36.6" clip-path="url(#terminal-3789115238-line-4)">@@@</text><text class="terminal-3789115238-r3" x="829.6" y="117.6" textLength="61" clip-path="url(#terminal-3789115238-line-4)">▔▔▔▔▔</text><text class="terminal-3789115238-r3" x="890.6" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">▔</text><text class="terminal-3789115238-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-4)">
+  </text><text class="terminal-3789115238-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-3789115238-line-5)">
+  </text><text class="terminal-3789115238-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-6)">
+  </text><text class="terminal-3789115238-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-7)">
+  </text><text class="terminal-3789115238-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-8)">
+  </text><text class="terminal-3789115238-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-9)">
+  </text><text class="terminal-3789115238-r11" x="61" y="264" textLength="12.2" clip-path="url(#terminal-3789115238-line-10)">+</text><text class="terminal-3789115238-r11" x="73.2" y="264" textLength="170.8" clip-path="url(#terminal-3789115238-line-10)">--------------</text><text class="terminal-3789115238-r11" x="244" y="264" textLength="12.2" clip-path="url(#terminal-3789115238-line-10)">+</text><text class="terminal-3789115238-r11" x="671" y="264" textLength="61" clip-path="url(#terminal-3789115238-line-10)">Title</text><text class="terminal-3789115238-r11" x="732" y="264" textLength="219.6" clip-path="url(#terminal-3789115238-line-10)">──────────────────</text><text class="terminal-3789115238-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-3789115238-line-10)">
+  </text><text class="terminal-3789115238-r11" x="61" y="288.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-11)">|</text><text class="terminal-3789115238-r1" x="73.2" y="288.4" textLength="170.8" clip-path="url(#terminal-3789115238-line-11)">had&#160;to&#160;fill&#160;up</text><text class="terminal-3789115238-r11" x="244" y="288.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-11)">|</text><text class="terminal-3789115238-r1" x="414.8" y="288.4" textLength="134.2" clip-path="url(#terminal-3789115238-line-11)">nine&#160;labels</text><text class="terminal-3789115238-r1" x="671" y="288.4" textLength="280.6" clip-path="url(#terminal-3789115238-line-11)">and&#160;ended&#160;up&#160;redoing&#160;it</text><text class="terminal-3789115238-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-11)">
+  </text><text class="terminal-3789115238-r11" x="61" y="312.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-12)">+</text><text class="terminal-3789115238-r11" x="85.4" y="312.8" textLength="48.8" clip-path="url(#terminal-3789115238-line-12)">Left</text><text class="terminal-3789115238-r11" x="146.4" y="312.8" textLength="97.6" clip-path="url(#terminal-3789115238-line-12)">--------</text><text class="terminal-3789115238-r11" x="244" y="312.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-12)">+</text><text class="terminal-3789115238-r11" x="671" y="312.8" textLength="183" clip-path="url(#terminal-3789115238-line-12)">───────────────</text><text class="terminal-3789115238-r11" x="854" y="312.8" textLength="97.6" clip-path="url(#terminal-3789115238-line-12)">Subtitle</text><text class="terminal-3789115238-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-12)">
+  </text><text class="terminal-3789115238-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-13)">
+  </text><text class="terminal-3789115238-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-14)">
+  </text><text class="terminal-3789115238-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-3789115238-line-15)">
+  </text><text class="terminal-3789115238-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-16)">
+  </text><text class="terminal-3789115238-r12" x="0" y="434.8" textLength="305" clip-path="url(#terminal-3789115238-line-17)">Title,&#160;but&#160;really&#160;looooo…</text><text class="terminal-3789115238-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-17)">
+  </text><text class="terminal-3789115238-r12" x="390.4" y="459.2" textLength="183" clip-path="url(#terminal-3789115238-line-18)">Title,&#160;but&#160;rea…</text><text class="terminal-3789115238-r12" x="695.4" y="459.2" textLength="231.8" clip-path="url(#terminal-3789115238-line-18)">Title,&#160;but&#160;really&#160;…</text><text class="terminal-3789115238-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-3789115238-line-18)">
+  </text><text class="terminal-3789115238-r1" x="24.4" y="483.6" textLength="256.2" clip-path="url(#terminal-3789115238-line-19)">because&#160;the&#160;first&#160;try</text><text class="terminal-3789115238-r1" x="390.4" y="483.6" textLength="183" clip-path="url(#terminal-3789115238-line-19)">had&#160;some&#160;labels</text><text class="terminal-3789115238-r1" x="695.4" y="483.6" textLength="231.8" clip-path="url(#terminal-3789115238-line-19)">that&#160;were&#160;too&#160;long.</text><text class="terminal-3789115238-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-3789115238-line-19)">
+  </text><text class="terminal-3789115238-r12" x="390.4" y="508" textLength="183" clip-path="url(#terminal-3789115238-line-20)">Subtitle,&#160;but&#160;…</text><text class="terminal-3789115238-r12" x="695.4" y="508" textLength="231.8" clip-path="url(#terminal-3789115238-line-20)">Subtitle,&#160;but&#160;real…</text><text class="terminal-3789115238-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-3789115238-line-20)">
+  </text><text class="terminal-3789115238-r12" x="0" y="532.4" textLength="305" clip-path="url(#terminal-3789115238-line-21)">Subtitle,&#160;but&#160;really&#160;loo…</text><text class="terminal-3789115238-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-3789115238-line-21)">
+  </text><text class="terminal-3789115238-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-3789115238-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
+# name: test_css_property[border_subtitle_align.py]
+  '''
+  <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-978241501-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-978241501-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-978241501-r1 { fill: #e1e1e1 }
+  .terminal-978241501-r2 { fill: #c5c8c6 }
+  .terminal-978241501-r3 { fill: #fea62b }
+  .terminal-978241501-r4 { fill: #ffffff }
+  .terminal-978241501-r5 { fill: #1e1e1e }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-978241501-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-978241501-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-978241501-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-978241501-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">BorderSubtitleAlignApp</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-978241501-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="25.9" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="50.3" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="74.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="329.4" y="74.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="634.4" y="74.7" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="99.1" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="36.6" y="123.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="109.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="122" y="123.5" width="829.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="172.3" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="196.7" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="221.1" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="353.8" y="221.1" width="256.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="610" y="221.1" width="341.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="245.5" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="269.9" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="427" y="269.9" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="536.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="549" y="269.9" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="318.7" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="343.1" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="367.5" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="329.4" y="367.5" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="634.4" y="367.5" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="391.9" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="416.3" width="817.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="841.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="854" y="416.3" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="939.4" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-978241501-matrix">
+      <text class="terminal-978241501-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-978241501-line-0)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="44.4" textLength="12.2" clip-path="url(#terminal-978241501-line-1)">┌</text><text class="terminal-978241501-r3" x="24.4" y="44.4" textLength="927.2" clip-path="url(#terminal-978241501-line-1)">────────────────────────────────────────────────────────────────────────────</text><text class="terminal-978241501-r3" x="951.6" y="44.4" textLength="12.2" clip-path="url(#terminal-978241501-line-1)">┐</text><text class="terminal-978241501-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-978241501-line-1)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="68.8" textLength="12.2" clip-path="url(#terminal-978241501-line-2)">│</text><text class="terminal-978241501-r3" x="951.6" y="68.8" textLength="12.2" clip-path="url(#terminal-978241501-line-2)">│</text><text class="terminal-978241501-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-978241501-line-2)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="93.2" textLength="12.2" clip-path="url(#terminal-978241501-line-3)">│</text><text class="terminal-978241501-r4" x="329.4" y="93.2" textLength="305" clip-path="url(#terminal-978241501-line-3)">My&#160;border&#160;is&#160;on&#160;the&#160;left.</text><text class="terminal-978241501-r3" x="951.6" y="93.2" textLength="12.2" clip-path="url(#terminal-978241501-line-3)">│</text><text class="terminal-978241501-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-978241501-line-3)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="117.6" textLength="12.2" clip-path="url(#terminal-978241501-line-4)">│</text><text class="terminal-978241501-r3" x="951.6" y="117.6" textLength="12.2" clip-path="url(#terminal-978241501-line-4)">│</text><text class="terminal-978241501-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-978241501-line-4)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="142" textLength="12.2" clip-path="url(#terminal-978241501-line-5)">└</text><text class="terminal-978241501-r3" x="36.6" y="142" textLength="73.2" clip-path="url(#terminal-978241501-line-5)">&lt;&#160;Left</text><text class="terminal-978241501-r3" x="122" y="142" textLength="829.6" clip-path="url(#terminal-978241501-line-5)">────────────────────────────────────────────────────────────────────</text><text class="terminal-978241501-r3" x="951.6" y="142" textLength="12.2" clip-path="url(#terminal-978241501-line-5)">┘</text><text class="terminal-978241501-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-978241501-line-5)">
+  </text><text class="terminal-978241501-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-978241501-line-6)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="190.8" textLength="12.2" clip-path="url(#terminal-978241501-line-7)">┏</text><text class="terminal-978241501-r3" x="24.4" y="190.8" textLength="927.2" clip-path="url(#terminal-978241501-line-7)">╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍</text><text class="terminal-978241501-r3" x="951.6" y="190.8" textLength="12.2" clip-path="url(#terminal-978241501-line-7)">┓</text><text class="terminal-978241501-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-978241501-line-7)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="215.2" textLength="12.2" clip-path="url(#terminal-978241501-line-8)">╏</text><text class="terminal-978241501-r3" x="951.6" y="215.2" textLength="12.2" clip-path="url(#terminal-978241501-line-8)">╏</text><text class="terminal-978241501-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-978241501-line-8)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="239.6" textLength="12.2" clip-path="url(#terminal-978241501-line-9)">╏</text><text class="terminal-978241501-r4" x="353.8" y="239.6" textLength="256.2" clip-path="url(#terminal-978241501-line-9)">My&#160;border&#160;is&#160;centered</text><text class="terminal-978241501-r3" x="951.6" y="239.6" textLength="12.2" clip-path="url(#terminal-978241501-line-9)">╏</text><text class="terminal-978241501-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-978241501-line-9)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="264" textLength="12.2" clip-path="url(#terminal-978241501-line-10)">╏</text><text class="terminal-978241501-r3" x="951.6" y="264" textLength="12.2" clip-path="url(#terminal-978241501-line-10)">╏</text><text class="terminal-978241501-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-978241501-line-10)">
+  </text><text class="terminal-978241501-r3" x="12.2" y="288.4" textLength="12.2" clip-path="url(#terminal-978241501-line-11)">┗</text><text class="terminal-978241501-r3" x="24.4" y="288.4" textLength="390.4" clip-path="url(#terminal-978241501-line-11)">╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍</text><text class="terminal-978241501-r3" x="427" y="288.4" textLength="109.8" clip-path="url(#terminal-978241501-line-11)">Centered!</text><text class="terminal-978241501-r3" x="549" y="288.4" textLength="402.6" clip-path="url(#terminal-978241501-line-11)">╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍</text><text class="terminal-978241501-r3" x="951.6" y="288.4" textLength="12.2" clip-path="url(#terminal-978241501-line-11)">┛</text><text class="terminal-978241501-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-978241501-line-11)">
+  </text><text class="terminal-978241501-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-978241501-line-12)">
+  </text><text class="terminal-978241501-r5" x="12.2" y="337.2" textLength="12.2" clip-path="url(#terminal-978241501-line-13)">▊</text><text class="terminal-978241501-r3" x="24.4" y="337.2" textLength="927.2" clip-path="url(#terminal-978241501-line-13)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-978241501-r3" x="951.6" y="337.2" textLength="12.2" clip-path="url(#terminal-978241501-line-13)">▎</text><text class="terminal-978241501-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-978241501-line-13)">
+  </text><text class="terminal-978241501-r5" x="12.2" y="361.6" textLength="12.2" clip-path="url(#terminal-978241501-line-14)">▊</text><text class="terminal-978241501-r3" x="951.6" y="361.6" textLength="12.2" clip-path="url(#terminal-978241501-line-14)">▎</text><text class="terminal-978241501-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-978241501-line-14)">
+  </text><text class="terminal-978241501-r5" x="12.2" y="386" textLength="12.2" clip-path="url(#terminal-978241501-line-15)">▊</text><text class="terminal-978241501-r4" x="329.4" y="386" textLength="305" clip-path="url(#terminal-978241501-line-15)">My&#160;border&#160;is&#160;on&#160;the&#160;right</text><text class="terminal-978241501-r3" x="951.6" y="386" textLength="12.2" clip-path="url(#terminal-978241501-line-15)">▎</text><text class="terminal-978241501-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-978241501-line-15)">
+  </text><text class="terminal-978241501-r5" x="12.2" y="410.4" textLength="12.2" clip-path="url(#terminal-978241501-line-16)">▊</text><text class="terminal-978241501-r3" x="951.6" y="410.4" textLength="12.2" clip-path="url(#terminal-978241501-line-16)">▎</text><text class="terminal-978241501-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-978241501-line-16)">
+  </text><text class="terminal-978241501-r5" x="12.2" y="434.8" textLength="12.2" clip-path="url(#terminal-978241501-line-17)">▊</text><text class="terminal-978241501-r3" x="24.4" y="434.8" textLength="817.4" clip-path="url(#terminal-978241501-line-17)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-978241501-r3" x="854" y="434.8" textLength="85.4" clip-path="url(#terminal-978241501-line-17)">Right&#160;&gt;</text><text class="terminal-978241501-r3" x="951.6" y="434.8" textLength="12.2" clip-path="url(#terminal-978241501-line-17)">▎</text><text class="terminal-978241501-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-978241501-line-17)">
+  </text><text class="terminal-978241501-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-978241501-line-18)">
+  </text><text class="terminal-978241501-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-978241501-line-19)">
+  </text><text class="terminal-978241501-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-978241501-line-20)">
+  </text><text class="terminal-978241501-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-978241501-line-21)">
+  </text><text class="terminal-978241501-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-978241501-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
+# name: test_css_property[border_title_align.py]
+  '''
+  <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-2738669032-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-2738669032-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-2738669032-r1 { fill: #e1e1e1 }
+  .terminal-2738669032-r2 { fill: #c5c8c6 }
+  .terminal-2738669032-r3 { fill: #fea62b }
+  .terminal-2738669032-r4 { fill: #ffffff }
+  .terminal-2738669032-r5 { fill: #1e1e1e }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-2738669032-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-2738669032-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-2738669032-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-2738669032-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">BorderTitleAlignApp</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-2738669032-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="36.6" y="25.9" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="109.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="122" y="25.9" width="829.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="50.3" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="74.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="329.4" y="74.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="634.4" y="74.7" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="99.1" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="123.5" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="172.3" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="427" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="536.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="549" y="172.3" width="402.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="196.7" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="221.1" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="353.8" y="221.1" width="256.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="610" y="221.1" width="341.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="245.5" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="12.2" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="269.9" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="318.7" width="817.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="841.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="854" y="318.7" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="939.4" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="343.1" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="367.5" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="329.4" y="367.5" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="634.4" y="367.5" width="317.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="391.9" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="24.4" y="416.3" width="927.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="951.6" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-2738669032-matrix">
+      <text class="terminal-2738669032-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-2738669032-line-0)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="44.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-1)">┌</text><text class="terminal-2738669032-r3" x="36.6" y="44.4" textLength="73.2" clip-path="url(#terminal-2738669032-line-1)">&lt;&#160;Left</text><text class="terminal-2738669032-r3" x="122" y="44.4" textLength="829.6" clip-path="url(#terminal-2738669032-line-1)">────────────────────────────────────────────────────────────────────</text><text class="terminal-2738669032-r3" x="951.6" y="44.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-1)">┐</text><text class="terminal-2738669032-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-1)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="68.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-2)">│</text><text class="terminal-2738669032-r3" x="951.6" y="68.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-2)">│</text><text class="terminal-2738669032-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-2)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="93.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-3)">│</text><text class="terminal-2738669032-r4" x="329.4" y="93.2" textLength="305" clip-path="url(#terminal-2738669032-line-3)">My&#160;border&#160;is&#160;on&#160;the&#160;left.</text><text class="terminal-2738669032-r3" x="951.6" y="93.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-3)">│</text><text class="terminal-2738669032-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-3)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="117.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-4)">│</text><text class="terminal-2738669032-r3" x="951.6" y="117.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-4)">│</text><text class="terminal-2738669032-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-4)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="142" textLength="12.2" clip-path="url(#terminal-2738669032-line-5)">└</text><text class="terminal-2738669032-r3" x="24.4" y="142" textLength="927.2" clip-path="url(#terminal-2738669032-line-5)">────────────────────────────────────────────────────────────────────────────</text><text class="terminal-2738669032-r3" x="951.6" y="142" textLength="12.2" clip-path="url(#terminal-2738669032-line-5)">┘</text><text class="terminal-2738669032-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-2738669032-line-5)">
+  </text><text class="terminal-2738669032-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-6)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="190.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-7)">┏</text><text class="terminal-2738669032-r3" x="24.4" y="190.8" textLength="390.4" clip-path="url(#terminal-2738669032-line-7)">╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍</text><text class="terminal-2738669032-r3" x="427" y="190.8" textLength="109.8" clip-path="url(#terminal-2738669032-line-7)">Centered!</text><text class="terminal-2738669032-r3" x="549" y="190.8" textLength="402.6" clip-path="url(#terminal-2738669032-line-7)">╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍</text><text class="terminal-2738669032-r3" x="951.6" y="190.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-7)">┓</text><text class="terminal-2738669032-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-7)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="215.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-8)">╏</text><text class="terminal-2738669032-r3" x="951.6" y="215.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-8)">╏</text><text class="terminal-2738669032-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-8)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="239.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-9)">╏</text><text class="terminal-2738669032-r4" x="353.8" y="239.6" textLength="256.2" clip-path="url(#terminal-2738669032-line-9)">My&#160;border&#160;is&#160;centered</text><text class="terminal-2738669032-r3" x="951.6" y="239.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-9)">╏</text><text class="terminal-2738669032-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-9)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="264" textLength="12.2" clip-path="url(#terminal-2738669032-line-10)">╏</text><text class="terminal-2738669032-r3" x="951.6" y="264" textLength="12.2" clip-path="url(#terminal-2738669032-line-10)">╏</text><text class="terminal-2738669032-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-2738669032-line-10)">
+  </text><text class="terminal-2738669032-r3" x="12.2" y="288.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-11)">┗</text><text class="terminal-2738669032-r3" x="24.4" y="288.4" textLength="927.2" clip-path="url(#terminal-2738669032-line-11)">╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍</text><text class="terminal-2738669032-r3" x="951.6" y="288.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-11)">┛</text><text class="terminal-2738669032-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-11)">
+  </text><text class="terminal-2738669032-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-12)">
+  </text><text class="terminal-2738669032-r5" x="12.2" y="337.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-13)">▊</text><text class="terminal-2738669032-r3" x="24.4" y="337.2" textLength="817.4" clip-path="url(#terminal-2738669032-line-13)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-2738669032-r3" x="854" y="337.2" textLength="85.4" clip-path="url(#terminal-2738669032-line-13)">Right&#160;&gt;</text><text class="terminal-2738669032-r3" x="951.6" y="337.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-13)">▎</text><text class="terminal-2738669032-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-13)">
+  </text><text class="terminal-2738669032-r5" x="12.2" y="361.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-14)">▊</text><text class="terminal-2738669032-r3" x="951.6" y="361.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-14)">▎</text><text class="terminal-2738669032-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-14)">
+  </text><text class="terminal-2738669032-r5" x="12.2" y="386" textLength="12.2" clip-path="url(#terminal-2738669032-line-15)">▊</text><text class="terminal-2738669032-r4" x="329.4" y="386" textLength="305" clip-path="url(#terminal-2738669032-line-15)">My&#160;border&#160;is&#160;on&#160;the&#160;right</text><text class="terminal-2738669032-r3" x="951.6" y="386" textLength="12.2" clip-path="url(#terminal-2738669032-line-15)">▎</text><text class="terminal-2738669032-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-2738669032-line-15)">
+  </text><text class="terminal-2738669032-r5" x="12.2" y="410.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-16)">▊</text><text class="terminal-2738669032-r3" x="951.6" y="410.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-16)">▎</text><text class="terminal-2738669032-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-16)">
+  </text><text class="terminal-2738669032-r5" x="12.2" y="434.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-17)">▊</text><text class="terminal-2738669032-r3" x="24.4" y="434.8" textLength="927.2" clip-path="url(#terminal-2738669032-line-17)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-2738669032-r3" x="951.6" y="434.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-17)">▎</text><text class="terminal-2738669032-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-17)">
+  </text><text class="terminal-2738669032-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-2738669032-line-18)">
+  </text><text class="terminal-2738669032-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-2738669032-line-19)">
+  </text><text class="terminal-2738669032-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-2738669032-line-20)">
+  </text><text class="terminal-2738669032-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-2738669032-line-21)">
+  </text><text class="terminal-2738669032-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-2738669032-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_css_property[box_sizing.py]
   '''
   <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
diff --git a/tests/test_border.py b/tests/test_border.py
index 37133125b3..c56687db2c 100644
--- a/tests/test_border.py
+++ b/tests/test_border.py
@@ -1,24 +1,231 @@
+import pytest
+from rich.console import Console
 from rich.segment import Segment
 from rich.style import Style
 
-from textual._border import render_row
+from textual._border import render_border_label, render_row
+from textual.widget import Widget
+
+_EMPTY_STYLE = Style()
+_BLANK_SEGMENT = Segment(" ", _EMPTY_STYLE)
+_WIDE_CONSOLE = Console(width=9999)
 
 
 def test_border_render_row():
     style = Style.parse("red")
     row = (Segment("┏", style), Segment("━", style), Segment("┓", style))
 
-    assert render_row(row, 5, False, False) == [Segment(row[1].text * 5, row[1].style)]
-    assert render_row(row, 5, True, False) == [
+    assert render_row(row, 5, False, False, ()) == [
+        Segment(row[1].text * 5, row[1].style)
+    ]
+    assert render_row(row, 5, True, False, ()) == [
         row[0],
         Segment(row[1].text * 4, row[1].style),
     ]
-    assert render_row(row, 5, False, True) == [
+    assert render_row(row, 5, False, True, ()) == [
         Segment(row[1].text * 4, row[1].style),
         row[2],
     ]
-    assert render_row(row, 5, True, True) == [
+    assert render_row(row, 5, True, True, ()) == [
         row[0],
         Segment(row[1].text * 3, row[1].style),
         row[2],
     ]
+
+
+def test_border_title_single_line():
+    """The border_title gets set to a single line even when multiple lines are provided."""
+    widget = Widget()
+
+    widget.border_title = ""
+    assert widget.border_title == ""
+
+    widget.border_title = "How is life\ngoing for you?"
+    assert widget.border_title == "How is life"
+
+    widget.border_title = "How is life\n\rgoing for you?"
+    assert widget.border_title == "How is life"
+
+    widget.border_title = "Sorry you \r\n have to \n read this."
+    assert widget.border_title == "Sorry you "
+
+    widget.border_title = "[red]This also \n works with markup \n involved.[/]"
+    assert widget.border_title == "[red]This also "
+
+
+def test_border_subtitle_single_line():
+    """The border_subtitle gets set to a single line even when multiple lines are provided."""
+    widget = Widget()
+
+    widget.border_subtitle = ""
+    assert widget.border_subtitle == ""
+
+    widget.border_subtitle = "How is life\ngoing for you?"
+    assert widget.border_subtitle == "How is life"
+
+    widget.border_subtitle = "How is life\n\rgoing for you?"
+    assert widget.border_subtitle == "How is life"
+
+    widget.border_subtitle = "Sorry you \r\n have to \n read this."
+    assert widget.border_subtitle == "Sorry you "
+
+    widget.border_subtitle = "[red]This also \n works with markup \n involved.[/]"
+    assert widget.border_subtitle == "[red]This also "
+
+
+@pytest.mark.parametrize(
+    ["width", "has_left_corner", "has_right_corner"],
+    [
+        (10, True, True),
+        (10, True, False),
+        (10, False, False),
+        (10, False, True),
+        (1, True, True),
+        (1, True, False),
+        (1, False, False),
+        (1, False, True),
+    ],
+)
+def test_render_border_label_empty_label_skipped(
+    width: int, has_left_corner: bool, has_right_corner: bool
+):
+    """Test that we get an empty list of segments if there is no label to display."""
+
+    assert [] == list(
+        render_border_label(
+            "",
+            True,
+            "round",
+            width,
+            _EMPTY_STYLE,
+            _EMPTY_STYLE,
+            _EMPTY_STYLE,
+            _WIDE_CONSOLE,
+            has_left_corner,
+            has_right_corner,
+        )
+    )
+
+
+@pytest.mark.parametrize(
+    ["label", "width", "has_left_corner", "has_right_corner"],
+    [
+        ("hey", 2, True, True),
+        ("hey", 2, True, False),
+        ("hey", 2, False, True),
+        ("hey", 3, True, True),
+        ("hey", 4, True, True),
+    ],
+)
+def test_render_border_label_skipped_if_narrow(
+    label: str, width: int, has_left_corner: bool, has_right_corner: bool
+):
+    """Test that we skip rendering a label when we do not have space for it.
+
+    In order for us to have enough space for the label, we need to have space for the
+    corners that we need (none, just one, or both) and we need to be able to have two
+    blank spaces around the label (one on each side).
+    If we don't have space for all of these, we skip the label altogether.
+    """
+
+    assert [] == list(
+        render_border_label(
+            label,
+            True,
+            "round",
+            width,
+            _EMPTY_STYLE,
+            _EMPTY_STYLE,
+            _EMPTY_STYLE,
+            _WIDE_CONSOLE,
+            has_left_corner,
+            has_right_corner,
+        )
+    )
+
+
+@pytest.mark.parametrize(
+    "label",
+    [
+        "Why did the scarecrow",
+        "win a Nobel prize?",
+        "because it was outstanding",
+        "in its field.",
+    ],
+)
+def test_render_border_label_wide_plain(label: str):
+    """Test label rendering in a wide area with no styling."""
+
+    BIG_NUM = 9999
+    args = (
+        True,
+        "round",
+        BIG_NUM,
+        _EMPTY_STYLE,
+        _EMPTY_STYLE,
+        _EMPTY_STYLE,
+        _WIDE_CONSOLE,
+        True,
+        True,
+    )
+    left, original_text, right = render_border_label(label, *args)
+
+    assert left == _BLANK_SEGMENT
+    assert right == _BLANK_SEGMENT
+    assert original_text == Segment(label, _EMPTY_STYLE)
+
+
+def test_render_border_label():
+    """Test label rendering with styling, with and without overflow."""
+
+    label = "[b][on red]What [i]is up[/on red] with you?[/]"
+    border_style = Style.parse("green on blue")
+
+    # Implicit test on the number of segments returned:
+    blank1, what, is_up, with_you, blank2 = render_border_label(
+        label,
+        True,
+        "round",
+        9999,
+        _EMPTY_STYLE,
+        _EMPTY_STYLE,
+        border_style,
+        _WIDE_CONSOLE,
+        True,
+        True,
+    )
+
+    expected_blank = Segment(" ", border_style)
+    assert blank1 == expected_blank
+    assert blank2 == expected_blank
+
+    what_style = Style.parse("b on red")
+    expected_what = Segment("What ", border_style + what_style)
+    assert what == expected_what
+
+    is_up_style = Style.parse("b on red i")
+    expected_is_up = Segment("is up", border_style + is_up_style)
+    assert is_up == expected_is_up
+
+    with_you_style = Style.parse("b i")
+    expected_with_you = Segment(" with you?", border_style + with_you_style)
+    assert with_you == expected_with_you
+
+    blank1, what, blank2 = render_border_label(
+        label,
+        True,
+        "round",
+        5 + 4,  # 5 where "What…" fits + 2 for the blank spaces + 2 for the corners.
+        _EMPTY_STYLE,
+        _EMPTY_STYLE,
+        border_style,
+        _WIDE_CONSOLE,
+        True,  # This corner costs 2 cells.
+        True,  # This corner costs 2 cells.
+    )
+
+    assert blank1 == expected_blank
+    assert blank2 == expected_blank
+
+    expected_what = Segment("What…", border_style + what_style)
+    assert what == expected_what
diff --git a/tests/test_styles_cache.py b/tests/test_styles_cache.py
index 01045e322b..5d1dda5272 100644
--- a/tests/test_styles_cache.py
+++ b/tests/test_styles_cache.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+from rich.console import Console
 from rich.segment import Segment
 from rich.style import Style
 
@@ -9,6 +10,8 @@
 from textual.geometry import Region, Size
 from textual.strip import Strip
 
+_CONSOLE = Console()
+
 
 def _extract_content(lines: list[Strip]) -> list[str]:
     """Extract the text content from lines."""
@@ -40,6 +43,9 @@ def test_no_styles():
         Color.parse("blue"),
         Color.parse("green"),
         content.__getitem__,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
     style = Style.from_color(bgcolor=Color.parse("green").rich_color)
@@ -67,6 +73,9 @@ def test_border():
         Color.parse("blue"),
         Color.parse("green"),
         content.__getitem__,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
 
@@ -98,6 +107,9 @@ def test_padding():
         Color.parse("blue"),
         Color.parse("green"),
         content.__getitem__,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
 
@@ -130,6 +142,9 @@ def test_padding_border():
         Color.parse("blue"),
         Color.parse("green"),
         content.__getitem__,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
 
@@ -163,6 +178,9 @@ def test_outline():
         Color.parse("blue"),
         Color.parse("green"),
         content.__getitem__,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
 
@@ -191,6 +209,9 @@ def test_crop():
         Color.parse("blue"),
         Color.parse("green"),
         content.__getitem__,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
         crop=Region(2, 2, 3, 3),
     )
@@ -227,7 +248,10 @@ def get_content_line(y: int) -> Strip:
         Color.parse("blue"),
         Color.parse("green"),
         get_content_line,
-        Size(3, 3),
+        _CONSOLE,
+        "",
+        "",
+        content_size=Size(3, 3),
     )
     assert rendered_lines == [0, 1, 2]
     del rendered_lines[:]
@@ -252,6 +276,9 @@ def get_content_line(y: int) -> Strip:
         Color.parse("blue"),
         Color.parse("green"),
         get_content_line,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
     assert rendered_lines == []
@@ -268,6 +295,9 @@ def get_content_line(y: int) -> Strip:
         Color.parse("blue"),
         Color.parse("green"),
         get_content_line,
+        _CONSOLE,
+        "",
+        "",
         content_size=Size(3, 3),
     )
     assert rendered_lines == [0, 1]


Code review comments:
diff_hunk: @@ -0,0 +1,38 @@
+This example shows all border title and subtitle alignments, together with some examples of how (sub)titles can have custom markup.
+Open the code tabs to see the details of the code examples.
+
+=== "Output"
+
+    ```{.textual path="docs/examples/styles/border_sub_title_align_all.py"}
+    ```
+
+=== "border_sub_title_align_all.py"
+
+    ```py hl_lines=""
path: docs/snippets/border_sub_title_align_all_example.md
text: @user1:
An empty `hl_lines` breaks the markup.

diff_hunk: @@ -0,0 +1,20 @@
+from textual.app import App
+from textual.widgets import Label
+
+
+class BorderSubtitleAlignApp(App):
+    def compose(self):
+        lbl = Label("My border is on the left.", id="label1")
path: docs/examples/styles/border_subtitle_align.py
text: @user1:
Suggest "My **subtitle** ..."

@author:
Right. I don't know why the heck I thought "border" was a good word, there.

diff_hunk: @@ -9,6 +10,8 @@
 from textual.geometry import Region, Size
 from textual.strip import Strip
 
+_CONSOLE = Console()
path: tests/test_styles_cache.py
text: @user1:
Probably not a good idea to share this across tests. Don't see any particular issues, but we don't want to share state accross tests.

Suggest just using `Console()` in the tests.

@author:
That was me trying to not waste time, since you recently told me `Console` does a fair bit of work.
That work was being wasted in the tests, so I thought it was a good idea to create a single `Console`.

But not sharing state between tests sounds more important.

diff_hunk: @@ -261,29 +274,170 @@ def get_box(
     )
 
 
-def render_row(
-    box_row: tuple[Segment, Segment, Segment], width: int, left: bool, right: bool
-) -> list[Segment]:
-    """Render a top, or bottom border row.
+def render_border_label(
+    label: str,
+    is_title: bool,
+    name: EdgeType,
+    width: int,
+    inner_style: Style,
+    outer_style: Style,
+    style: Style,
+    console: Console,
+    has_left_corner: bool,
+    has_right_corner: bool,
+) -> Iterable[Segment]:
+    """Render a border label (the title or subtitle) with optional markup.
+
+    The styling that may be embedded in the label will be reapplied after taking into
+    account the inner, outer, and border-specific, styles.
+
+    Args:
+        label: The label to display (that may contain markup).
+        is_title: Whether we are rendering the title (`True`) or the subtitle (`False`).
+        name: Name of the box type.
+        width: The width, in cells, of the space available for the whole edge.
+            This is the total space that may also be needed for the border corners and
+            the whitespace padding around the (sub)title. Thus, the effective space
+            available for the border label is:
+            - `width` if no corner is needed;
+            - `width - 2` if one corner is needed; and
+            - `width - 4` if both corners are needed.
+        inner_style: The inner style (widget background).
+        outer_style: The outer style (parent background).
+        style: Widget style.
+        console: The console that will render the markup in the label.
+        has_left_corner: Whether the border edge will have to render a left corner.
+        has_right_corner: Whether the border edge will have to render a right corner.
+
+    Returns:
+        A list of segments that represent the full label and surrounding padding.
+    """
+    # How many cells do we need to reserve for surrounding blanks and corners?
+    corners_needed = has_left_corner + has_right_corner
+    cells_reserved = 2 * corners_needed
+    if not label or width <= cells_reserved:
+        yield from ()
path: src/textual/_border.py
text: @user1:
I suspect this line is not required.

diff_hunk: @@ -257,6 +257,22 @@ There are many other border types. Run the following from the command prompt to
 textual borders
 ```
 
+
+#### Border labels (title and subtitle)
+
+A widget that has a border set can have a title in the top edge of the border.
+For that, set the attribute [`border_title`][textual.widget.Widget.border_title] in your widget.
+Similarly, a widget with a border can have a subtitle in its bottom edge.
+To change it, assign to the attribute [`border_subtitle`][textual.widget.Widget.border_subtitle].
+
+These labels must be one-line strings, but they can contain rich markup.
+Additionally, the labels can be aligned horizontally with the respective styles:
+
+ - [`border-title-align`](../styles/border_title_align.md) for the title; and
+ - [`border-subtitle-align`](../styles/border_subtitle_align.md) for the subtitle.
+
+--8<-- "docs/snippets/border_sub_title_align_all_example.md"
path: docs/guide/styles.md
text: @user1:
This example is too complicated for this section.

Fine as a reference, but TMI at this point.

Suggest a simple example with single centered box, a title, and subtitle. Just enough to illustrated what the style may do.

@author:
I added a title and a subtitle to the previous border example (for continuity) instead of making a brand new example.