Components/Scroll
Universal Component

Scroll

`@better-scroll/scroll-bar` powered `TxScroll` container.

This page was migrated by AI, please review carefully

Migration is complete, but please validate against source code and manual review.

Scroll

TxScroll is the current scroll container powered by @better-scroll/scroll-bar, with native-mode fallbacks for platform-specific scrolling behavior.

Behavior Notes

  • BetterScroll Mode
    • Supports bounce with a more consistent scrollbar appearance
    • Content/container changes auto refresh() (configurable via refreshOnContentChange)
  • Native Mode
    • Uses native browser scrolling
    • direction maps to overflow-x/y to keep structure consistent
    • macOS Safari uses native scrolling directly
    • Automatically falls back to native scrolling on macOS + Chromium 145+ (Chromium already supports it natively)
    • Set unified=true to force BetterScroll takeover across environments

Runtime Strategy (Priority)

  1. native=true: always use native scrolling (highest priority).
  2. unified=true: always let BetterScroll take over (consistent cross-environment behavior).
  3. Running on macOS Safari: use native scrolling directly.
  4. nativeAutoFallback=true and macOS + Chromium >= 145: auto switch to native scrolling.
  5. Otherwise: use BetterScroll.

Basic Usage

Scroll

Demo will load when visible.
<template>
  <div class="demo-container" style="height: 220px;">
    <div style="height: 100%;">
      <TxScroll style="height: 100%;">
        <div style="height: 1200px; display: flex; flex-direction: column; gap: 8px;">
          <div v-for="i in 60" :key="i" class="demo-scroll-item">
            Row {{ i }}
          </div>
        </div>
      </TxScroll>
    </div>
  </div>
</template>

Direction and Scrollbar

Scroll (horizontal)

Demo will load when visible.
<template>
  <div class="demo-container" style="height: 140px;">
    <div style="height: 100%;">
      <TxScroll direction="horizontal" style="height: 100%;">
        <div style="width: 920px; height: 100%; display: flex; align-items: center; gap: 12px;">
          <div v-for="i in 24" :key="i" class="demo-scroll-item" style="min-width: 120px;">
            Col {{ i }}
          </div>
        </div>
      </TxScroll>
    </div>
  </div>
</template>

Scroll (bounce + always show scrollbar)

Demo will load when visible.
<template>
  <div class="demo-container" style="height: 220px;">
    <div style="height: 100%;">
      <TxScroll :bounce="true" :scrollbar-always-visible="true" style="height: 100%;">
        <div style="height: 160px; display: flex; flex-direction: column; gap: 8px;">
          <div class="demo-scroll-item">
            Short content
          </div>
          <div class="demo-scroll-item">
            No boundary but scrollbar should be visible
          </div>
        </div>
      </TxScroll>
    </div>
  </div>
</template>

Scroll Chaining (Advanced)

By default inner scrolling does not propagate to the outer container (even at edges). Enable scrollChaining if you want scroll to bubble at the top/bottom.

⚠️ We do not recommend enabling scrollChaining by default. In nested scrolling layouts, it can make users unsure which layer is actually scrolling. Enable it only when parent-child scroll handoff is explicitly required.

Scroll (scroll chaining, advanced)

Demo will load when visible.
<template>
  <div class="demo-container" style="height: 320px;">
    <div style="height: 100%; overflow: auto; padding: 12px; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.12);">
      <div style="height: 720px; display: flex; flex-direction: column; gap: 12px;">
        <div class="demo-scroll-item">
          Outer scroll area (top)
        </div>

        <div style="height: 160px; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.12); overflow: hidden;">
          <TxScroll style="height: 100%;" :bounce="true">
            <div style="height: 520px; display: flex; flex-direction: column; gap: 8px;">
              <div class="demo-scroll-item">
                Inner (default): scrollChaining = false
              </div>
              <div v-for="i in 18" :key="i" class="demo-scroll-item">
                Item {{ i }}
              </div>
            </div>
          </TxScroll>
        </div>

        <div class="demo-scroll-item">
          Outer content (middle)
        </div>

        <div style="height: 160px; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.12); overflow: hidden;">
          <TxScroll style="height: 100%;" :bounce="true" :scroll-chaining="true">
            <div style="height: 520px; display: flex; flex-direction: column; gap: 8px;">
              <div class="demo-scroll-item">
                Inner (opt-in): scrollChaining = true
              </div>
              <div v-for="i in 18" :key="i" class="demo-scroll-item">
                Item {{ i }}
              </div>
            </div>
          </TxScroll>
        </div>

        <div class="demo-scroll-item">
          Outer scroll area (bottom)
        </div>
      </div>
    </div>
  </div>
</template>

Use Native Scrolling

If you do not need BetterScroll behavior, switch to native scrolling (keeps the same container structure).

Scroll (native)

Demo will load when visible.
<template>
  <div class="demo-container" style="height: 220px;">
    <div style="height: 100%;">
      <TxScroll native style="height: 100%;">
        <div style="height: 1200px; display: flex; flex-direction: column; gap: 8px;">
          <div v-for="i in 60" :key="i" class="demo-scroll-item">
            Native Row {{ i }}
          </div>
        </div>
      </TxScroll>
    </div>
  </div>
</template>

Pull-to-Refresh and Load More

  • Pull-to-refresh: enable pullDownRefresh, listen to @pulling-down, then call finishPullDown() when done.
  • Load more: enable pullUpLoad, listen to @pulling-up, then call finishPullUp() when done.
  • In native=true or auto-native fallback mode, events are degraded triggers; pullDownStop only works in BetterScroll mode.

Scroll (pull down + pull up)

Demo will load when visible.
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(Array.from({ length: 30 }, (_, i) => i + 1))
const scrollRef = ref<any>(null)
const loading = ref(false)

function sleep(ms: number) {
  return new Promise<void>(resolve => setTimeout(resolve, ms))
}

async function onPullingDown() {
  loading.value = true
  await sleep(600)
  items.value = Array.from({ length: 30 }, (_, i) => i + 1)
  loading.value = false
  scrollRef.value?.finishPullDown?.()
}

async function onPullingUp() {
  if (loading.value)
    return
  loading.value = true
  await sleep(600)
  const last = items.value[items.value.length - 1] ?? 0
  items.value.push(...Array.from({ length: 20 }, (_, i) => last + i + 1))
  loading.value = false
  scrollRef.value?.finishPullUp?.()
}
</script>

<template>
  <div class="demo-container" style="height: 260px;">
    <TxScroll
      ref="scrollRef"
      style="height: 100%;"
      :pull-down-refresh="true"
      :pull-up-load="true"
      @pulling-down="onPullingDown"
      @pulling-up="onPullingUp"
    >
      <div style="display: flex; flex-direction: column; gap: 8px;">
        <div v-for="i in items" :key="i" class="demo-scroll-item">
          Item {{ i }}
        </div>
      </div>

      <template #footer>
        <div style="padding: 10px 0; color: var(--tx-text-color-secondary); text-align: center;">
          {{ loading ? 'Loading...' : 'Pull up to load more' }}
        </div>
      </template>
    </TxScroll>
  </div>
</template>

API

Props

PropTypeDefaultDescription
nativebooleanfalseForce native browser scrolling and skip BetterScroll initialization.
unifiedbooleanfalseForce BetterScroll takeover for consistent cross-environment behavior (overrides Safari/Chromium auto-native; native=true still wins).
nativeAutoFallbackbooleantrueControls auto-native fallback on macOS + Chromium 145+; does not affect the macOS Safari native-first rule.
noPaddingbooleanfalseRemove the default content padding; horizontal and both-axis modes also set content width to max-content.
scrollChainingbooleanfalseAllow parent scroll handoff at boundaries; not recommended by default, enable only when nested handoff is explicitly needed.
direction'vertical' | 'horizontal' | 'both''vertical'Select the scroll axes; native mode maps this to overflow-x/y, BetterScroll maps it to scrollX, scrollY, and freeScroll.
scrollbarbooleantrueEnable the BetterScroll scrollbar plugin; native mode uses browser scrollbars.
scrollbarFadebooleantruePass BetterScroll scrollbar fade behavior through when scrollbar is enabled.
scrollbarInteractivebooleantrueAllow BetterScroll scrollbar dragging when scrollbar is enabled.
scrollbarAlwaysVisiblebooleanfalseKeep the BetterScroll wrapper in always-visible scrollbar state.
scrollbarMinSizenumber18Set the minimum BetterScroll scrollbar thumb size via --tx-scrollbar-min-size.
probeType0 | 1 | 2 | 33BetterScroll probeType
bouncebooleantrueEnable BetterScroll boundary bounce and the local wheel overshoot behavior.
clickbooleantruePass BetterScroll click handling through.
wheelbooleantrueEnable the local wheel bridge for BetterScroll mode; ctrl wheel gestures are ignored.
refreshOnContentChangebooleantrueObserve content mutations and call BetterScroll refresh() after changes.
pullDownRefreshboolean | Record<string, unknown>falseEnable pull-down refresh; BetterScroll receives plugin options, native mode emits a threshold-based trigger.
pullDownThresholdnumber70Minimum native touch delta and BetterScroll plugin threshold for pulling-down.
pullDownStopnumber56BetterScroll pull-down stop position; native mode does not use this value.
pullUpLoadboolean | Record<string, unknown>falseEnable pull-up load; BetterScroll receives plugin options, native mode emits when the bottom threshold is reached.
pullUpThresholdnumber0Native distance-to-bottom threshold and BetterScroll plugin threshold for pulling-up.
optionsRecord<string, unknown>{}Extra BetterScroll options; wheelOvershoot is consumed locally before options are passed through.

Events

EventParamsDescription
scroll{ scrollTop: number; scrollLeft: number }Emits absolute scroll offsets from native mode or BetterScroll position updates.
pulling-down-Emits once per pull-down cycle until finishPullDown() is called.
pulling-up-Emits once per pull-up cycle until finishPullUp() is called.