Scroll Area

Augments native scroll functionality for custom, cross-browser styling and supports virtual scrolling for large lists.

Component Features

  • Custom scrollbar styling across all browsers.
  • Supports vertical, horizontal, or both directions.
  • Smooth scrolling support.
  • Scroll position and edge detection events.
  • Virtual Scrolling support for rendering massive lists efficiently.

Both Directions

<script setup lang="ts">
import { BrScrollArea } from '@breezeui/vue'

const artworks = [
  {
    artist: 'Ornella Binni',
    art: 'https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80',
  },
  {
    artist: 'Tom Byrom',
    art: 'https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80',
  },
  {
    artist: 'Vladimir Malyavko',
    art: 'https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80',
  },
]
</script>

<template>
  <BrScrollArea class="h-[200px] w-full max-w-[350px] whitespace-nowrap rounded-md border" direction="both" type="always">
    <div class="flex w-max space-x-4 p-4">
      <figure v-for="artwork in artworks" :key="artwork.artist" class="shrink-0">
        <div class="overflow-hidden rounded-md">
          <img
            :src="artwork.art"
            :alt="artwork.artist"
            class="aspect-[3/4] h-fit w-fit object-cover"
            width="150"
            height="200"
          >
        </div>
        <figcaption class="pt-2 text-xs text-muted-foreground">
          Photo by <span class="font-semibold text-foreground">{{ artwork.artist }}</span>
        </figcaption>
      </figure>
    </div>
  </BrScrollArea>
</template>

Smooth Scroll & Events

<script setup lang="ts">
import { BrScrollArea, BrButton } from '@breezeui/vue'

const scrollTo = (id: string) => {
  const el = document.getElementById(id)
  if (el) {
    el.scrollIntoView({ behavior: 'smooth', block: 'start' })
  }
}

const handleScrollTop = () => { console.log('Scrolled to top') }
const handleScrollBottom = () => { console.log('Scrolled to bottom') }
const handleScrollPosition = (pos: { x: number; y: number }) => { 
  // console.log('Position:', pos) 
}
</script>

<template>
  <div>
    <div class="flex gap-2 mb-4">
      <BrButton size="sm" @click="scrollTo('section-1')">Section 1</BrButton>
      <BrButton size="sm" @click="scrollTo('section-2')">Section 2</BrButton>
      <BrButton size="sm" @click="scrollTo('section-3')">Section 3</BrButton>
    </div>
    <BrScrollArea 
      smooth 
      class="h-[250px] w-full max-w-[400px] rounded-md border p-4"
      @scroll-top="handleScrollTop"
      @scroll-bottom="handleScrollBottom"
      @scroll-position="handleScrollPosition"
    >
      <div class="space-y-8">
        <div id="section-1" class="h-[200px] rounded bg-primary/10 p-4 flex items-center justify-center">Section 1</div>
        <div id="section-2" class="h-[200px] rounded bg-secondary/10 p-4 flex items-center justify-center">Section 2</div>
        <div id="section-3" class="h-[200px] rounded bg-destructive/10 p-4 flex items-center justify-center">Section 3</div>
      </div>
    </BrScrollArea>
  </div>
</template>

With Table

<script setup lang="ts">
import { BrScrollArea, BrCard, BrCardHeader, BrCardTitle, BrCardContent, BrTable } from '@breezeui/vue'

const tableColumns = [
  { key: 'invoice', title: 'Invoice', width: 100 },
  { key: 'status', title: 'Status' },
  { key: 'method', title: 'Method' },
  { key: 'amount', title: 'Amount', align: 'right' },
]

const tableData = Array.from({ length: 15 }).map((_, i) => ({
  invoice: `INV00${i + 1}`,
  status: 'Paid',
  method: 'Credit Card',
  amount: `$${(Math.random() * 1000).toFixed(2)}`,
}))
</script>

<template>
  <BrCard class="w-full max-w-[800px]">
    <BrCardHeader>
      <BrCardTitle>Recent Orders</BrCardTitle>
    </BrCardHeader>
    <BrCardContent>
      <BrScrollArea class="h-[300px] w-full rounded-md border" direction="both">
        <div class="min-w-[800px]">
          <BrTable :columns="tableColumns" :data="tableData" />
        </div>
      </BrScrollArea>
    </BrCardContent>
  </BrCard>
</template>

Infinite Scroll

<script setup lang="ts">
import { ref } from 'vue'
import { BrScrollArea } from '@breezeui/vue'

const infiniteList = ref(Array.from({ length: 20 }).map((_, i) => `Item ${i + 1}`))
const isLoadingMore = ref(false)

const handleInfiniteScroll = () => {
  if (isLoadingMore.value) return
  isLoadingMore.value = true
  
  // Simulate network request
  setTimeout(() => {
    const currentLength = infiniteList.value.length
    const moreItems = Array.from({ length: 10 }).map((_, i) => `Item ${currentLength + i + 1}`)
    infiniteList.value.push(...moreItems)
    isLoadingMore.value = false
  }, 1000)
}
</script>

<template>
  <BrScrollArea 
    class="h-[300px] w-full max-w-[400px] rounded-md border p-4"
    :scroll-threshold="50"
    @scroll-bottom="handleInfiniteScroll"
  >
    <div class="space-y-4">
      <div 
        v-for="item in infiniteList" 
        :key="item"
        class="rounded bg-primary/5 p-4 flex items-center justify-between"
      >
        <span>{{ item }}</span>
      </div>
      
      <div v-if="isLoadingMore" class="py-4 flex justify-center items-center text-sm text-muted-foreground">
        <span class="animate-pulse">Loading more...</span>
      </div>
    </div>
  </BrScrollArea>
</template>

Virtual Scrolling

You can enable virtual scrolling to render lists with thousands of items without performance issues.

<script setup lang="ts">
import { BrScrollArea } from '@breezeui/vue'

const virtualItems = Array.from({ length: 10000 }).map((_, i) => ({
  id: i,
  text: `Virtual Item ${i + 1}`,
}))
</script>

<template>
  <BrScrollArea 
    class="h-[300px] w-full max-w-[400px] rounded-md border bg-card"
    virtual-scroll
    :items="virtualItems"
    :item-height="40"
  >
    <template #item="{ item, index }">
      <div class="flex items-center px-4 h-[40px] text-sm border-b border-border/50 last:border-b-0 hover:bg-muted/50 transition-colors">
        <span class="text-muted-foreground/70 w-12 text-xs">{{ index + 1 }}</span>
        <span class="font-normal text-foreground">{{ item.text }}</span>
      </div>
    </template>
  </BrScrollArea>
</template>

API

Props

NameTypeDefaultDescription
type'auto' | 'always' | 'scroll' | 'hover''hover'Describes the nature of scrollbar visibility.
direction'vertical' | 'horizontal' | 'both''vertical'The direction of the scroll area.
smoothbooleanfalseEnables smooth scrolling behavior.
scrollThresholdnumber0Distance in pixels from the edge to trigger infinite scroll events.
virtualScrollbooleanfalseEnables virtual scrolling mode.
itemsany[][]Array of items to render when virtual scrolling is enabled.
itemHeightnumber40Fixed height of each item in pixels for virtual scrolling.

Events

NameParametersDescription
scroll-position(position: { x: number; y: number })Emitted when scrolling occurs, providing current coordinates.
scroll-top-Emitted when scrolled to the top edge (considering threshold).
scroll-bottom-Emitted when scrolled to the bottom edge (considering threshold).
scroll-left-Emitted when scrolled to the left edge (considering threshold).
scroll-right-Emitted when scrolled to the right edge (considering threshold).

Slots

NameScopeDescription
default-The main content to be scrolled.
item{ item: any, index: number }Custom template for each item in virtual scroll mode.