Skip to content

Commit 08a7e26

Browse files
authored
Merge pull request #6156 from Textualize/scrollbar-visibility
scrollbar visibility rule
2 parents a77700c + 0198b63 commit 08a7e26

File tree

15 files changed

+453
-7
lines changed

15 files changed

+453
-7
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## Unreleased
9+
10+
### Added
11+
12+
- Added scrollbar-visibility rule https://github.com/Textualize/textual/pull/6156
13+
814
## [6.2.1] - 2025-10-01
915

1016
- Fix inability to copy text outside of an input/textarea when it was focused https://github.com/Textualize/textual/pull/6148
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from textual.app import App
2+
from textual.containers import Horizontal, VerticalScroll
3+
from textual.widgets import Label
4+
5+
TEXT = """I must not fear.
6+
Fear is the mind-killer.
7+
Fear is the little-death that brings total obliteration.
8+
I will face my fear.
9+
I will permit it to pass over me and through me.
10+
And when it has gone past, I will turn the inner eye to see its path.
11+
Where the fear has gone there will be nothing. Only I will remain.
12+
"""
13+
14+
15+
class ScrollbarApp(App):
16+
CSS_PATH = "scrollbar_visibility.tcss"
17+
18+
def compose(self):
19+
yield Horizontal(
20+
VerticalScroll(Label(TEXT * 10), classes="left"),
21+
VerticalScroll(Label(TEXT * 10), classes="right"),
22+
)
23+
24+
25+
if __name__ == "__main__":
26+
app = ScrollbarApp()
27+
app.run()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
VerticalScroll {
2+
width: 1fr;
3+
}
4+
5+
.left {
6+
scrollbar-visibility: visible; # The default
7+
}
8+
9+
.right {
10+
scrollbar-visibility: hidden;
11+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Scrollbar-visibility
2+
3+
The `scrollbar-visibility` is used to show or hide scrollbars.
4+
5+
If scrollbars are hidden, the user may still scroll the container using the mouse wheel / keys / and gestures, but
6+
there will be no scrollbars shown.
7+
8+
## Syntax
9+
10+
--8<-- "docs/snippets/syntax_block_start.md"
11+
scrollbar-visibility: hidden | visible;
12+
--8<-- "docs/snippets/syntax_block_end.md"
13+
14+
15+
### Values
16+
17+
| Value | Description |
18+
| ------------------- | ---------------------------------------------------- |
19+
| `hidden` | The widget's scrollbars will be hidden. |
20+
| `visible` (default) | The widget's scrollbars will be displayed as normal. |
21+
22+
23+
## Examples
24+
25+
The following example contains two containers with the same text.
26+
The container on the right has its scrollbar hidden.
27+
28+
=== "Output"
29+
30+
```{.textual path="docs/examples/styles/scrollbar_visibility.py"}
31+
```
32+
33+
=== "scrollbar_visibility.py"
34+
35+
```py
36+
--8<-- "docs/examples/styles/scrollbar_visibility.py"
37+
```
38+
39+
=== "scrollbar_visibility.tcss"
40+
41+
```css
42+
--8<-- "docs/examples/styles/scrollbar_visibility.tcss"
43+
```
44+
45+
46+
## CSS
47+
48+
```css
49+
scrollbar-visibility: visible;
50+
scrollbar-visibility: hidden;
51+
```
52+
53+
54+
55+
## Python
56+
57+
```py
58+
widget.styles.scrollbar_visibility = "visible";
59+
widget.styles.scrollbar_visibility = "hidden";
60+
```

mkdocs-nav.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ nav:
136136
- "styles/scrollbar_colors/scrollbar_corner_color.md"
137137
- "styles/scrollbar_gutter.md"
138138
- "styles/scrollbar_size.md"
139+
- "styles/scrollbar_visibility.md"
139140
- "styles/text_align.md"
140141
- "styles/text_opacity.md"
141142
- "styles/text_overflow.md"

src/textual/_compositor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ def add_widget(
694694
if (
695695
widget.show_vertical_scrollbar
696696
or widget.show_horizontal_scrollbar
697-
):
697+
) and styles.scrollbar_visibility == "visible":
698698
for chrome_widget, chrome_region in widget._arrange_scrollbars(
699699
container_region
700700
):

src/textual/css/_styles_builder.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
VALID_OVERLAY,
5353
VALID_POSITION,
5454
VALID_SCROLLBAR_GUTTER,
55+
VALID_SCROLLBAR_VISIBILITY,
5556
VALID_STYLE_FLAGS,
5657
VALID_TEXT_ALIGN,
5758
VALID_TEXT_OVERFLOW,
@@ -76,6 +77,7 @@
7677
Display,
7778
EdgeType,
7879
Overflow,
80+
ScrollbarVisibility,
7981
TextOverflow,
8082
TextWrap,
8183
Visibility,
@@ -768,6 +770,13 @@ def process_color(self, name: str, tokens: list[Token]) -> None:
768770
process_scrollbar_background_hover = process_color
769771
process_scrollbar_background_active = process_color
770772

773+
def process_scrollbar_visibility(self, name: str, tokens: list[Token]) -> None:
774+
"""Process scrollbar visibility rules."""
775+
self.styles._rules["scrollbar_visibility"] = cast(
776+
ScrollbarVisibility,
777+
self._process_enum(name, tokens, VALID_SCROLLBAR_VISIBILITY),
778+
)
779+
771780
process_link_color = process_color
772781
process_link_background = process_color
773782
process_link_color_hover = process_color

src/textual/css/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
VALID_TEXT_WRAP: Final = {"wrap", "nowrap"}
9191
VALID_TEXT_OVERFLOW: Final = {"clip", "fold", "ellipsis"}
9292
VALID_EXPAND: Final = {"greedy", "optimal"}
93+
VALID_SCROLLBAR_VISIBILITY: Final = {"visible", "hidden"}
9394

9495
HATCHES: Final = {
9596
"left": "╲",

src/textual/css/styles.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
VALID_OVERLAY,
4949
VALID_POSITION,
5050
VALID_SCROLLBAR_GUTTER,
51+
VALID_SCROLLBAR_VISIBILITY,
5152
VALID_TEXT_ALIGN,
5253
VALID_TEXT_OVERFLOW,
5354
VALID_TEXT_WRAP,
@@ -153,11 +154,10 @@ class RulesMap(TypedDict, total=False):
153154
scrollbar_background: Color
154155
scrollbar_background_hover: Color
155156
scrollbar_background_active: Color
156-
157157
scrollbar_gutter: ScrollbarGutter
158-
159158
scrollbar_size_vertical: int
160159
scrollbar_size_horizontal: int
160+
scrollbar_visibility: ScrollbarVisibility
161161

162162
align_horizontal: AlignHorizontal
163163
align_vertical: AlignVertical
@@ -242,6 +242,7 @@ class StylesBase:
242242
"scrollbar_background",
243243
"scrollbar_background_hover",
244244
"scrollbar_background_active",
245+
"scrollbar_visibility",
245246
"link_color",
246247
"link_background",
247248
"link_color_hover",
@@ -424,6 +425,10 @@ class StylesBase:
424425
"""Set the width of the vertical scrollbar (measured in cells)."""
425426
scrollbar_size_horizontal = IntegerProperty(default=1, layout=True)
426427
"""Set the height of the horizontal scrollbar (measured in cells)."""
428+
scrollbar_visibility = StringEnumProperty(
429+
VALID_SCROLLBAR_VISIBILITY, "visible", layout=True
430+
)
431+
"""Sets the visibility of the scrollbar."""
427432

428433
align_horizontal = StringEnumProperty(
429434
VALID_ALIGN_HORIZONTAL, "left", layout=True, refresh_children=True
@@ -1153,6 +1158,8 @@ def append_declaration(name: str, value: str) -> None:
11531158
append_declaration(
11541159
"scrollbar-size-vertical", str(self.scrollbar_size_vertical)
11551160
)
1161+
if "scrollbar_visibility" in rules:
1162+
append_declaration("scrollbar-visibility", self.scrollbar_visibility)
11561163

11571164
if "box_sizing" in rules:
11581165
append_declaration("box-sizing", self.box_sizing)

src/textual/css/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
TextWrap = Literal["wrap", "nowrap"]
4545
TextOverflow = Literal["clip", "fold", "ellipsis"]
4646
Expand = Literal["greedy", "expand"]
47+
ScrollbarVisibility = Literal["visible", "hidden"]
4748

4849
Specificity3 = Tuple[int, int, int]
4950
Specificity6 = Tuple[int, int, int, int, int, int]

0 commit comments

Comments
 (0)