Skip to content

Scrollbar

This component provides a unified scrollbar experience because native browser scrollbars often look inconsistent across browsers.

Basic Usage

Wrap content with b-scrollbar. The default slot is the content area, as shown below:

Scroll Demo Block 1
Scroll Demo Block 2
Scroll Demo Block 3
Scroll Demo Block 4
Scroll Demo Block 5
Scroll Demo Block 6
Scroll Demo Block 7
Scroll Demo Block 8
Scroll Demo Block 9
Scroll Demo Block 10
Scroll Demo Block 11
Scroll Demo Block 12
<template>
  <div style="height: 300px">
    <b-scrollbar ref="componentScrollBar" style="height: 100%">
      <div class="scrollbar-demo-list">
        <div v-for="i in 12" :key="i" class="scrollbar-demo-item">Scroll Demo Block {{ i }}</div>
      </div>
    </b-scrollbar>
  </div>
</template>

<style scoped>
.scrollbar-demo-list {
  padding: 4px;
}

.scrollbar-demo-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  margin: 10px 0;
  border-radius: 8px;
  background: linear-gradient(135deg, #f8fbff 0%, #eef5ff 100%);
  color: #3a4a66;
  font-weight: 500;
}
</style>

Note: If the content does not exceed the container height, no scrollbar is rendered.

Max Height

Use max-height to limit the maximum height of the scroll area. The scrollbar only appears when the content exceeds this height.

Item 1

Item 2

Item 3

Item 4

Item 5

<template>
  <div>
    <div style="margin-bottom: 12px">
      <b-button size="small" @click="add">Add Item</b-button>
      <b-button size="small" style="margin-left: 8px" @click="remove">Delete Item</b-button>
    </div>
    <b-scrollbar max-height="320px">
      <p v-for="item in count" :key="item" class="scrollbar-demo-item">
        Item {{ item }}
      </p>
    </b-scrollbar>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(5)

function add() {
  count.value++
}

function remove() {
  if (count.value > 0) {
    count.value--
  }
}
</script>

<style scoped>
.scrollbar-demo-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  margin: 10px 0;
  border-radius: 6px;
  background: #f5f7fa;
  color: #606266;
}
</style>

Manual Scroll

Use the exposed setScrollTop and setScrollLeft methods to control the scroll position manually.

Manual Scroll 1

Manual Scroll 2

Manual Scroll 3

Manual Scroll 4

Manual Scroll 5

Manual Scroll 6

Manual Scroll 7

Manual Scroll 8

Manual Scroll 9

Manual Scroll 10

Manual Scroll 11

Manual Scroll 12

Manual Scroll 13

Manual Scroll 14

Manual Scroll 15

Manual Scroll 16

Manual Scroll 17

Manual Scroll 18

Manual Scroll 19

Manual Scroll 20

<template>
  <div>
    <b-scrollbar ref="scrollbarRef" height="320px" always @scroll="handleScroll">
      <div ref="innerRef">
        <p v-for="item in 20" :key="item" class="scrollbar-demo-item">
          Manual Scroll {{ item }}
        </p>
      </div>
    </b-scrollbar>

    <div style="margin-top: 16px">
      <b-slider v-model="value" :max="max" :format-tooltip="formatTooltip"></b-slider>
    </div>
  </div>
</template>

<script setup lang="ts">
import { nextTick, onMounted, ref, watch } from 'vue'

const max = ref(0)
const value = ref(0)
const innerRef = ref<HTMLElement | null>(null)
const scrollbarRef = ref<{
  setScrollTop: (scrollTop: number) => void
  wrapRef?: {
    value: HTMLElement | null
  }
} | null>(null)

function handleScroll({ scrollTop }: { scrollTop: number }) {
  value.value = scrollTop
}

function formatTooltip(currentValue: number) {
  return `${currentValue}px`
}

function updateMaxScroll() {
  const wrapElement = scrollbarRef.value?.wrapRef?.value
  const contentHeight = innerRef.value?.clientHeight || 0
  const viewportHeight = wrapElement?.clientHeight || 0

  max.value = Math.max(contentHeight - viewportHeight, 0)
}

watch(value, currentValue => {
  scrollbarRef.value?.setScrollTop(currentValue)
})

onMounted(async () => {
  await nextTick()
  updateMaxScroll()
})
</script>

<style scoped>
.scrollbar-demo-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  margin: 10px 0;
  border-radius: 6px;
  background: #f5f7fa;
  color: #606266;
}
</style>

Always Show

Use always to keep the scrollbar visible instead of showing it only on hover.

Always Visible Block 1
Always Visible Block 2
Always Visible Block 3
Always Visible Block 4
Always Visible Block 5
Always Visible Block 6
Always Visible Block 7
Always Visible Block 8
Always Visible Block 9
Always Visible Block 10
Always Visible Block 11
Always Visible Block 12
<template>
  <div style="height: 300px">
    <b-scrollbar ref="componentScrollBar" always noresize>
      <div class="scrollbar-demo-list">
        <div v-for="i in 12" :key="i" class="scrollbar-demo-item">Always Visible Block {{ i }}</div>
      </div>
    </b-scrollbar>
  </div>
</template>

<style scoped>
.scrollbar-demo-list {
  padding: 4px;
}

.scrollbar-demo-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  margin: 10px 0;
  border-radius: 8px;
  background: linear-gradient(135deg, #f8fbff 0%, #eef5ff 100%);
  color: #3a4a66;
  font-weight: 500;
}
</style>

Custom Scrollbar Styles

You can customize the scrollbar with props or override its styles with CSS.

Custom Styled Block 1
Custom Styled Block 2
Custom Styled Block 3
Custom Styled Block 4
Custom Styled Block 5
Custom Styled Block 6
Custom Styled Block 7
Custom Styled Block 8
Custom Styled Block 9
Custom Styled Block 10
Custom Styled Block 11
Custom Styled Block 12
<template>
  <div style="height: 300px">
    <b-scrollbar
      ref="componentScrollBar"
      always
      noresize
      :bar-style="{ background: 'rgba(110, 23, 122, 0.3)' }"
      :bar-wrap-style="{ background: 'rgba(0, 0, 0, 0.03)' }"
    >
      <div class="scrollbar-demo-list">
        <div v-for="i in 12" :key="i" class="scrollbar-demo-item">Custom Styled Block {{ i }}</div>
      </div>
    </b-scrollbar>
  </div>
</template>

<style scoped>
.scrollbar-demo-list {
  padding: 4px;
}

.scrollbar-demo-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  margin: 10px 0;
  border-radius: 8px;
  background: linear-gradient(135deg, #f7efff 0%, #efe4ff 100%);
  color: #5c2a86;
  font-weight: 500;
}
</style>

Infinite Scroll

end-reached is triggered when the scrollbar reaches an edge. It supports top / bottom / left / right and is commonly used for infinite loading.

Infinite Scroll Item 1

Infinite Scroll Item 2

Infinite Scroll Item 3

Infinite Scroll Item 4

Infinite Scroll Item 5

Infinite Scroll Item 6

Infinite Scroll Item 7

Infinite Scroll Item 8

Infinite Scroll Item 9

Infinite Scroll Item 10

Infinite Scroll Item 11

Infinite Scroll Item 12

Infinite Scroll Item 13

Infinite Scroll Item 14

Infinite Scroll Item 15

Infinite Scroll Item 16

Infinite Scroll Item 17

Infinite Scroll Item 18

Infinite Scroll Item 19

Infinite Scroll Item 20

<template>
  <b-scrollbar height="320px" :distance="16" @end-reached="loadMore">
    <p v-for="item in count" :key="item" class="scrollbar-demo-item">
      Infinite Scroll Item {{ item }}
    </p>
  </b-scrollbar>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(20)

function loadMore(direction: 'top' | 'bottom' | 'left' | 'right') {
  if (direction === 'bottom') {
    count.value += 5
  }
}
</script>

<style scoped>
.scrollbar-demo-item {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  margin: 10px 0;
  border-radius: 6px;
  background: #f5f7fa;
  color: #606266;
}
</style>

Notes

  • The parent container of b-scrollbar should have a fixed height
  • b-scrollbar itself should usually be set to height: 100%
  • If an unwanted horizontal scrollbar appears, add .bin-scrollbar__wrap { overflow-x: hidden; }

Props

ParameterDescriptionTypeOptionsDefault
distanceTrigger distance for end-reached in pixelsNumber-0
heightHeight of the scroll areaString / Number-
maxHeightMaximum height of the scroll areaString / Number-
nativeWhether to use native scrollingBooleantruefalse
alwaysWhether to always show the scrollbar instead of only on hoverBooleantruefalse
wrapStyleInline style for the wrap containerString / Object / Array-
wrapClassClass name for the wrap containerString / Array-
viewClassClass name for the view containerString / Array-
viewStyleInline style for the view containerString / Object / Array-
noresizeIf the container size does not change, setting this to true can optimize performanceBooleantruefalse
tagElement tag of the view containerString-div
minSizeMinimum scrollbar sizeNumber-20
tabindexTabindex of the wrap containerString / Number-
barStyleScrollbar thumb styleObject-{}
barWrapStyleScrollbar bar container styleObject-{}

Events

Event NameDescriptionCallback
scrollTriggered when scrolling{ scrollTop, scrollLeft }
end-reachedTriggered when an edge is reacheddirection: 'top' | 'bottom' | 'left' | 'right'

Methods

Method NameDescriptionParameters
handleScrollManually trigger scroll handling
scrollToScroll to a specific position(options) or (x, y)
setScrollTopSet vertical scroll distance(scrollTop: number)
setScrollLeftSet horizontal scroll distance(scrollLeft: number)
updateManually update scrollbar state
wrapRefScroll container instance ref