Scroll Area

A scrollable region with a thin, themed native scrollbar.

import { … } from '@inlaylab/ivorie/scroll-area'
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
Item 21
Item 22
Item 23
Item 24
Item 25
Item 26
Item 27
Item 28
Item 29
Item 30
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_SCROLL_AREA_IMPORTS } from '@inlaylab/ivorie/scroll-area';

@Component({
  selector: 'demo-scroll-area',
  imports: [IV_SCROLL_AREA_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-scroll-area maxHeight="240px" class="w-64 rounded-md border">
      @for (item of items; track item) {
        <div class="border-b px-3 py-2 text-sm last:border-b-0">{{ item }}</div>
      }
    </iv-scroll-area>
  `,
})
export default class ScrollAreaDemo {
  protected readonly items = Array.from({ length: 30 }, (_, i) => `Item ${i + 1}`);
}

Installation

CLInpm
npx ivorie add scroll-area

Ports the shadcn/ui ScrollArea. It uses the browser's native scrolling and styles the scrollbar with CSS. No measuring, so it works with SSR.

Usage

import { IV_SCROLL_AREA_IMPORTS } from '@inlaylab/ivorie/scroll-area';
<iv-scroll-area maxHeight="300px">
  <div class="px-3 py-2 text-sm">Long content</div>
</iv-scroll-area>

Examples

Horizontal

Card 1
Card 2
Card 3
Card 4
Card 5
Card 6
Card 7
Card 8
Card 9
Card 10
Card 11
Card 12
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_SCROLL_AREA_IMPORTS } from '@inlaylab/ivorie/scroll-area';

@Component({
  selector: 'demo-scroll-area-horizontal',
  imports: [IV_SCROLL_AREA_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-scroll-area type="scroll-x" class="w-80 rounded-md border">
      <div class="flex w-max gap-3 p-3">
        @for (col of columns; track col) {
          <div class="flex h-20 w-32 shrink-0 items-center justify-center rounded-md bg-muted text-sm">{{ col }}</div>
        }
      </div>
    </iv-scroll-area>
  `,
})
export default class ScrollAreaHorizontalDemo {
  protected readonly columns = Array.from({ length: 12 }, (_, i) => `Card ${i + 1}`);
}

Both axes

Cell 1
Cell 2
Cell 3
Cell 4
Cell 5
Cell 6
Cell 7
Cell 8
Cell 9
Cell 10
Cell 11
Cell 12
Cell 13
Cell 14
Cell 15
Cell 16
Cell 17
Cell 18
Cell 19
Cell 20
Cell 21
Cell 22
Cell 23
Cell 24
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_SCROLL_AREA_IMPORTS } from '@inlaylab/ivorie/scroll-area';

@Component({
  selector: 'demo-scroll-area-both-axes',
  imports: [IV_SCROLL_AREA_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-scroll-area type="always" maxHeight="200px" maxWidth="320px" class="rounded-md border">
      <div class="grid w-[640px] grid-cols-4 gap-2 p-3 text-sm">
        @for (cell of cells; track cell) {
          <div class="rounded-md bg-muted p-3">{{ cell }}</div>
        }
      </div>
    </iv-scroll-area>
  `,
})
export default class ScrollAreaBothAxesDemo {
  protected readonly cells = Array.from({ length: 24 }, (_, i) => `Cell ${i + 1}`);
}

Fill parent

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
Item 21
Item 22
Item 23
Item 24
Item 25
Item 26
Item 27
Item 28
Item 29
Item 30
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_SCROLL_AREA_IMPORTS } from '@inlaylab/ivorie/scroll-area';

@Component({
  selector: 'demo-scroll-area-fill-parent',
  imports: [IV_SCROLL_AREA_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="h-56 w-64">
      <iv-scroll-area class="h-full rounded-md border">
        @for (item of items; track item) {
          <div class="px-3 py-2 text-sm">{{ item }}</div>
        }
      </iv-scroll-area>
    </div>
  `,
})
export default class ScrollAreaFillParentDemo {
  protected readonly items = Array.from({ length: 30 }, (_, i) => `Item ${i + 1}`);
}

API

Every part also accepts class.

iv-scroll-area

Name Type Default Description
type 'auto' | 'always' | 'scroll-x' | 'scroll-y' 'auto' auto: scrollbars when content overflows. always: scrollbars always shown. scroll-x: horizontal only. scroll-y: vertical only.
maxHeight string '' CSS max-height of the viewport, for example '300px' or '50vh'.
maxWidth string '' CSS max-width of the viewport.

Accessibility

  • The viewport has tabindex="0", so keyboard users can focus it and scroll with the arrow, Page and Home/End keys.
  • A focus ring shows on :focus-visible.

Notes

  • class goes on the host. The viewport fills the host (size-full) and inherits its border radius, so set rounded-* on the host.
  • For a fixed height, set maxHeight or add an h-* class to the host.
  • The scrollbar is 10px, with a transparent track and a --border colored thumb. Firefox uses scrollbar-width: thin.
  • Styles target the .iv-scroll-area-viewport class only. They do not affect other scrollable elements.