diff --git a/docs/pages/components/tab-group.md b/docs/pages/components/tab-group.md
index f85b27b7..6fb3ac9d 100644
--- a/docs/pages/components/tab-group.md
+++ b/docs/pages/components/tab-group.md
@@ -411,6 +411,148 @@ const App = () => (
);
```
+### Fixed scroll controls
+
+When tabs are scrolled all the way to one side, the scroll button on that side can't be clicked. Set the `fixed-scroll-controls` attribute to keep the effected button visible in that case.
+
+```html:preview
+
+ Tab 1
+ Tab 2
+ Tab 3
+ Tab 4
+ Tab 5
+ Tab 6
+ Tab 7
+ Tab 8
+ Tab 9
+ Tab 10
+ Tab 11
+ Tab 12
+ Tab 13
+ Tab 14
+ Tab 15
+ Tab 16
+ Tab 17
+ Tab 18
+ Tab 19
+ Tab 20
+
+ Tab panel 1
+ Tab panel 2
+ Tab panel 3
+ Tab panel 4
+ Tab panel 5
+ Tab panel 6
+ Tab panel 7
+ Tab panel 8
+ Tab panel 9
+ Tab panel 10
+ Tab panel 11
+ Tab panel 12
+ Tab panel 13
+ Tab panel 14
+ Tab panel 15
+ Tab panel 16
+ Tab panel 17
+ Tab panel 18
+ Tab panel 19
+ Tab panel 20
+
+```
+
+```jsx:react
+import SlTab from '@shoelace-style/shoelace/dist/react/tab';
+import SlTabGroup from '@shoelace-style/shoelace/dist/react/tab-group';
+import SlTabPanel from '@shoelace-style/shoelace/dist/react/tab-panel';
+
+const App = () => (
+
+
+ Tab 1
+
+
+ Tab 2
+
+
+ Tab 3
+
+
+ Tab 4
+
+
+ Tab 5
+
+
+ Tab 6
+
+
+ Tab 7
+
+
+ Tab 8
+
+
+ Tab 9
+
+
+ Tab 10
+
+
+ Tab 11
+
+
+ Tab 12
+
+
+ Tab 13
+
+
+ Tab 14
+
+
+ Tab 15
+
+
+ Tab 16
+
+
+ Tab 17
+
+
+ Tab 18
+
+
+ Tab 19
+
+
+ Tab 20
+
+
+ Tab panel 1
+ Tab panel 2
+ Tab panel 3
+ Tab panel 4
+ Tab panel 5
+ Tab panel 6
+ Tab panel 7
+ Tab panel 8
+ Tab panel 9
+ Tab panel 10
+ Tab panel 11
+ Tab panel 12
+ Tab panel 13
+ Tab panel 14
+ Tab panel 15
+ Tab panel 16
+ Tab panel 17
+ Tab panel 18
+ Tab panel 19
+ Tab panel 20
+
+);
+```
+
### Manual Activation
When focused, keyboard users can press [[Left]] or [[Right]] to select the desired tab. By default, the corresponding tab panel will be shown immediately (automatic activation). You can change this behavior by setting `activation="manual"` which will require the user to press [[Space]] or [[Enter]] before showing the tab panel (manual activation).
diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md
index aa864124..2c531b89 100644
--- a/docs/pages/resources/changelog.md
+++ b/docs/pages/resources/changelog.md
@@ -14,6 +14,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti
## Next
+- Scroll buttons for `` auto hide when they are not clickable. The `fixed-scroll-controls` attribute can be included to prevent this behavior. [#2128]
- Added support for using `` in `` default slot [#2015]
- Added the `countdown` attribute to `` to show a visual indicator before the toast disappears [#1899]
- Fixed a bug that caused errors to show in the console when components disconnect before before `firstUpdated()` executes [#2127]
diff --git a/src/components/tab-group/tab-group.component.ts b/src/components/tab-group/tab-group.component.ts
index c0806e84..1a93bcd9 100644
--- a/src/components/tab-group/tab-group.component.ts
+++ b/src/components/tab-group/tab-group.component.ts
@@ -1,7 +1,8 @@
+import '../../internal/scrollend-polyfill.js';
import { classMap } from 'lit/directives/class-map.js';
+import { eventOptions, property, query, state } from 'lit/decorators.js';
import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
-import { property, query, state } from 'lit/decorators.js';
import { scrollIntoView } from '../../internal/scroll.js';
import { watch } from '../../internal/watch.js';
import componentStyles from '../../styles/component.styles.js';
@@ -61,6 +62,9 @@ export default class SlTabGroup extends ShoelaceElement {
@state() private hasScrollControls = false;
+ @state() private shouldHideScrollStartButton = false;
+ @state() private shouldHideScrollEndButton = false;
+
/** The placement of the tabs. */
@property() placement: 'top' | 'bottom' | 'start' | 'end' = 'top';
@@ -73,6 +77,9 @@ export default class SlTabGroup extends ShoelaceElement {
/** Disables the scroll arrows that appear when tabs overflow. */
@property({ attribute: 'no-scroll-controls', type: Boolean }) noScrollControls = false;
+ /** Prevent scroll buttons from being hidden when inactive. */
+ @property({ attribute: 'fixed-scroll-controls', type: Boolean }) fixedScrollControls = false;
+
connectedCallback() {
const whenAllDefined = Promise.all([
customElements.whenDefined('sl-tab'),
@@ -366,6 +373,28 @@ export default class SlTabGroup extends ShoelaceElement {
return nextTab;
}
+ /**
+ * The reality of the browser means that we can't expect the scroll position to be exactly what we want it to be, so
+ * we add one pixel of wiggle room to our calculations.
+ */
+ private scrollOffset = 1;
+
+ @eventOptions({ passive: true })
+ private updateScrollButtons() {
+ if (this.hasScrollControls && !this.fixedScrollControls) {
+ this.shouldHideScrollStartButton = this.scrollFromStart() <= this.scrollOffset;
+ this.shouldHideScrollEndButton = this.isScrolledToEnd();
+ }
+ }
+
+ private isScrolledToEnd() {
+ return this.scrollFromStart() + this.nav.clientWidth >= this.nav.scrollWidth - this.scrollOffset;
+ }
+
+ private scrollFromStart() {
+ return this.localize.dir() === 'rtl' ? -this.nav.scrollLeft : this.nav.scrollLeft;
+ }
+
@watch('noScrollControls', { waitUntilFirstUpdate: true })
updateScrollControls() {
if (this.noScrollControls) {
@@ -379,6 +408,8 @@ export default class SlTabGroup extends ShoelaceElement {
this.hasScrollControls =
['top', 'bottom'].includes(this.placement) && this.nav.scrollWidth > this.nav.clientWidth + 1;
}
+
+ this.updateScrollButtons();
}
@watch('placement', { waitUntilFirstUpdate: true })
@@ -426,16 +457,22 @@ export default class SlTabGroup extends ShoelaceElement {
`
: ''}
-
+
@@ -449,9 +486,15 @@ export default class SlTabGroup extends ShoelaceElement {
diff --git a/src/components/tab-group/tab-group.styles.ts b/src/components/tab-group/tab-group.styles.ts
index 7180ec33..4330bf47 100644
--- a/src/components/tab-group/tab-group.styles.ts
+++ b/src/components/tab-group/tab-group.styles.ts
@@ -31,6 +31,11 @@ export default css`
padding: 0 var(--sl-spacing-x-large);
}
+ .tab-group--has-scroll-controls .tab-group__scroll-button--start--hidden,
+ .tab-group--has-scroll-controls .tab-group__scroll-button--end--hidden {
+ visibility: hidden;
+ }
+
.tab-group__body {
display: block;
overflow: auto;