Skip to main content

upp-scrollable

A scroll container component that provides configurable scroll behavior for its content. It supports horizontal scrolling, vertical scrolling, both directions, or no scrolling at all. CSS classes are applied dynamically based on the selected direction, and it integrates with viewService for viewport-aware adjustments.

When to Use

Use upp-scrollable to wrap content that may overflow its container and needs controlled scroll behavior. It is particularly useful inside upp-panel-content or any fixed-height container where you want explicit control over which scroll directions are enabled.

In any mode other than 'none' the area also supports mobile-style click-and-drag with the mouse: the user presses on the content and drags to pan the view along the enabled axis. The behaviour is symmetric for the horizontal and vertical axes — the component disables native text selection and HTML5 drag on its own area while in a draggable mode, so the gesture is not intercepted by selection or image/link drag and the only effect is the pan controlled by the component.

Demo

Source Code

<h2>upp-scrollable</h2>
<p class="demo-description">Message feed with configurable scroll direction via <code>upp-scrollable</code>.</p>

<div class="demo-controls">
<span class="control-label">Direction:</span>
<ion-button size="small" [fill]="direction === 'none' ? 'solid' : 'outline'" (click)="setDirection('none')">none</ion-button>
<ion-button size="small" [fill]="direction === 'x' ? 'solid' : 'outline'" (click)="setDirection('x')">x</ion-button>
<ion-button size="small" [fill]="direction === 'y' ? 'solid' : 'outline'" (click)="setDirection('y')">y</ion-button>
<ion-button size="small" [fill]="direction === 'both' ? 'solid' : 'outline'" (click)="setDirection('both')">both</ion-button>
</div>

<div class="demo-section">
<h3>Messages (scrollbar={{ direction }})</h3>
<div class="feed-container">
<upp-scrollable [scrollbar]="direction">
<div [class]="direction === 'x' ? 'messages-row' : 'messages-column'">
<div class="message-card" *ngFor="let msg of messages">
<div class="message-header">
<span class="message-sender">{{ msg.sender }}</span>
<span class="message-time">{{ msg.time }}</span>
</div>
<div class="message-text">{{ msg.text }}</div>
</div>
</div>
</upp-scrollable>
</div>
</div>

API Reference

upp-scrollable

PropertyTypeDefaultDescription
scrollbar'none' | 'x' | 'y' | 'both''none'Controls which scroll directions are enabled. 'none' hides all scrollbars, 'x' enables horizontal only, 'y' enables vertical only, and 'both' enables both directions.
content'auto' | 'fill''auto'Layout of the projected content slot. 'auto' keeps it a normal block (children stack, the area scrolls on overflow). 'fill' turns the slot into a vertical flex column that stretches to the full scroll height, so a projected child can grow to fill the space (used by upp-form in layout='fill'). Avoids penetrating .upp-scrollable-content with ::ng-deep.

Horizontal (scrollbar="x"): the host keeps content height (height: auto) and does not grow to fill a column-flex parent, so it fits slim rows (e.g. under upp-panel-header). --upp-scrollable-bottom-gap defaults to 0px in this mode; set it on the host if you still need trailing space.

CSS (host): --upp-scrollable-max-height — optional; when set by a parent (e.g. upp-select on the list’s upp-scrollable), the host uses it as max-height so content taller than the cap scrolls inside .upp-scrollable-content. Use a length or var(--your-token).

The trailing spacer after projected content uses --upp-scrollable-bottom-gap (default 200px for y / both / none, class .bottom-gap). Ancestors can override it (for example upp-select sets 0px on the list’s upp-scrollable). upp-form keeps 0px by default; when it contains upp-form-buttons with float="bottom" (class --bottom), it sets --upp-scrollable-bottom-gap to 200px so floating action buttons do not overlap the last fields. Standalone upp-form-buttons outside a form still use the internal .end-gap spacer (same 200px default).

The leading spacer before projected content uses --upp-scrollable-top-gap (default 0px, class .top-gap). Set it on the host or an ancestor when the first item should not sit flush with the top edge. upp-panel-content applies --upp-panel-content-offset-top (default 0px) below the header when a panel needs explicit start spacing; most main panels rely on their own content padding instead. upp-form leaves the scrollable top gap at 0px so spacing is not doubled. Use --upp-scrollable-top-gap on a specific upp-scrollable or upp-form when a panel needs extra start padding beyond the panel content offset.

The component dynamically applies CSS classes based on the scrollbar value:

scrollbar valueCSS classes applied
'none'--no-scrollbar-x --no-scrollbar-y
'x'--no-scrollbar-y --scroll-x-only
'y'--no-scrollbar-x
'both'(none)

When content="fill", the host also gets --fill-content, which makes the projected slot (.scroll-slot) a vertical flex column.