Skip to main content
Nexus Design System
GitHub

ScrollArea

ScrollArea gives a fixed-size region custom scrollbars that look the same on every platform. Use it for panels, lists and drawers with overflowing content. For a full page, let the browser scroll the page.

Release notes

Scroll areas render a consistent custom scrollbar across Chrome, Safari, and Firefox, replacing the platform scrollbar so the chrome looks the same everywhere.

The viewport measures its content and only surfaces a scrollbar on the axis that actually overflows — a wide-but-short block gets a horizontal bar and no vertical one.

Scrollbars fade in on hover by default. Pass type="always" to keep them pinned, or type="scroll" to show them only while the content is moving.

The thumb is sized in proportion to the visible fraction of the content, so a long document gets a short thumb and a barely-overflowing one gets a long thumb.

Keyboard focus can move into the viewport with a visible focus ring, so the scrollable region stays reachable without a pointer.

'use client';

import { ScrollArea } from '@/components/nxds/components/scroll-area/scroll-area';

const releaseNotes = [
  'Scroll areas render a consistent custom scrollbar across Chrome, Safari, and Firefox, replacing the platform scrollbar so the chrome looks the same everywhere.',
  'The viewport measures its content and only surfaces a scrollbar on the axis that actually overflows — a wide-but-short block gets a horizontal bar and no vertical one.',
  'Scrollbars fade in on hover by default. Pass type="always" to keep them pinned, or type="scroll" to show them only while the content is moving.',
  'The thumb is sized in proportion to the visible fraction of the content, so a long document gets a short thumb and a barely-overflowing one gets a long thumb.',
  'Keyboard focus can move into the viewport with a visible focus ring, so the scrollable region stays reachable without a pointer.',
];

export default function ScrollAreaDefault() {
  return (
    <ScrollArea className="nx:h-48 nx:w-72 nx:rounded-md nx:border-default nx:border-border-default">
      <div className="nx:flex nx:flex-col nx:gap-3 nx:p-4 nx:typography-label-default nx:text-foreground">
        <h4 className="nx:font-medium nx:leading-none">Release notes</h4>
        {releaseNotes.map((note, i) => (
          <p key={i} className="nx:text-muted-foreground">
            {note}
          </p>
        ))}
      </div>
    </ScrollArea>
  );
}

Installation

npx nxds-cli@latest add scroll-area

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import { ScrollArea, ScrollBar } from '@/components/nxds/components/scroll-area/scroll-area';

Composition

ScrollArea
└── ScrollBar

Examples

Vertical list

Scroll a long list of rows inside a fixed height.

Builds

  • v1.4.0-canary.40
  • v1.4.0-canary.39
  • v1.4.0-canary.38
  • v1.4.0-canary.37
  • v1.4.0-canary.36
  • v1.4.0-canary.35
  • v1.4.0-canary.34
  • v1.4.0-canary.33
  • v1.4.0-canary.32
  • v1.4.0-canary.31
  • v1.4.0-canary.30
  • v1.4.0-canary.29
  • v1.4.0-canary.28
  • v1.4.0-canary.27
  • v1.4.0-canary.26
  • v1.4.0-canary.25
  • v1.4.0-canary.24
  • v1.4.0-canary.23
  • v1.4.0-canary.22
  • v1.4.0-canary.21
  • v1.4.0-canary.20
  • v1.4.0-canary.19
  • v1.4.0-canary.18
  • v1.4.0-canary.17
  • v1.4.0-canary.16
  • v1.4.0-canary.15
  • v1.4.0-canary.14
  • v1.4.0-canary.13
  • v1.4.0-canary.12
  • v1.4.0-canary.11
  • v1.4.0-canary.10
  • v1.4.0-canary.9
  • v1.4.0-canary.8
  • v1.4.0-canary.7
  • v1.4.0-canary.6
  • v1.4.0-canary.5
  • v1.4.0-canary.4
  • v1.4.0-canary.3
  • v1.4.0-canary.2
  • v1.4.0-canary.1
'use client';

import { ScrollArea } from '@/components/nxds/components/scroll-area/scroll-area';

const versions = Array.from(
  { length: 40 },
  (_, i) => `v1.4.0-canary.${40 - i}`
);

export default function ScrollAreaVerticalList() {
  return (
    <ScrollArea className="nx:h-72 nx:w-56 nx:rounded-md nx:border-default nx:border-border-default">
      <div className="nx:p-4">
        <h4 className="nx:mb-3 nx:typography-label-default nx:leading-none nx:text-foreground">
          Builds
        </h4>
        <ul className="nx:flex nx:flex-col">
          {versions.map((version) => (
            <li
              key={version}
              className="nx:border-b-default nx:border-border-default nx:py-2 nx:typography-label-default nx:text-foreground nx:last:border-b-0"
            >
              {version}
            </li>
          ))}
        </ul>
      </div>
    </ScrollArea>
  );
}

Horizontal row

Add a horizontal ScrollBar to scroll a row of items sideways.

Aurora
by Ornella Binni
Tide
by Tom Byrom
Drift
by Vladimir Malyavko
Ember
by Mara Stein
Quartz
by Liang Wu
Meadow
by Sofia Russo
'use client';

import { ScrollArea, ScrollBar } from '@/components/nxds/components/scroll-area/scroll-area';

const artworks = [
  { title: 'Aurora', artist: 'Ornella Binni' },
  { title: 'Tide', artist: 'Tom Byrom' },
  { title: 'Drift', artist: 'Vladimir Malyavko' },
  { title: 'Ember', artist: 'Mara Stein' },
  { title: 'Quartz', artist: 'Liang Wu' },
  { title: 'Meadow', artist: 'Sofia Russo' },
];

export default function ScrollAreaHorizontalRow() {
  return (
    <ScrollArea className="nx:w-96 nx:rounded-md nx:border-default nx:border-border-default nx:whitespace-nowrap">
      <div className="nx:flex nx:w-max nx:gap-4 nx:p-4">
        {artworks.map((artwork) => (
          <figure key={artwork.title} className="nx:shrink-0">
            <div className="nx:flex nx:h-40 nx:w-32 nx:items-end nx:rounded-md nx:bg-muted nx:p-3">
              <span className="nx:typography-label-default nx:text-foreground">
                {artwork.title}
              </span>
            </div>
            <figcaption className="nx:pt-2 nx:typography-label-small nx:text-muted-foreground">
              by {artwork.artist}
            </figcaption>
          </figure>
        ))}
      </div>
      <ScrollBar orientation="horizontal" />
    </ScrollArea>
  );
}

Both

Scroll a large grid on both axes with two scrollbars.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
'use client';

import { ScrollArea, ScrollBar } from '@/components/nxds/components/scroll-area/scroll-area';

export default function ScrollAreaBoth() {
  return (
    <ScrollArea className="nx:h-72 nx:w-96 nx:rounded-md nx:border-default nx:border-border-default">
      <div className="nx:w-max nx:p-4">
        {Array.from({ length: 20 }, (_, row) => (
          <div key={row} className="nx:flex nx:gap-2 nx:py-1">
            {Array.from({ length: 15 }, (_, col) => (
              <div
                key={col}
                className="nx:flex nx:size-12 nx:shrink-0 nx:items-center nx:justify-center nx:rounded-sm nx:bg-muted nx:typography-label-small nx:text-muted-foreground"
              >
                {row * 15 + col}
              </div>
            ))}
          </div>
        ))}
      </div>
      <ScrollBar orientation="horizontal" />
    </ScrollArea>
  );
}

Visible affordance

Set type="always" so dense panels show their scrollbars without a hover.

RunBranchOwnerDurationStatus
run-4820mainMira3m 10sPassed
run-4819release/vegaOmar4m 17sQueued
run-4818codex/ui-polishJules5m 24sReviewing
run-4817docs/mwgNia6m 31sBlocked
run-4816mainMira7m 38sPassed
run-4815release/vegaOmar8m 45sQueued
run-4814codex/ui-polishJules9m 52sReviewing
run-4813docs/mwgNia10m 59sBlocked
run-4812mainMira11m 16sPassed
run-4811release/vegaOmar3m 23sQueued
run-4810codex/ui-polishJules4m 30sReviewing
run-4809docs/mwgNia5m 37sBlocked
run-4808mainMira6m 44sPassed
run-4807release/vegaOmar7m 51sQueued
run-4806codex/ui-polishJules8m 58sReviewing
run-4805docs/mwgNia9m 15sBlocked
run-4804mainMira10m 22sPassed
run-4803release/vegaOmar11m 29sQueued
run-4802codex/ui-polishJules3m 36sReviewing
run-4801docs/mwgNia4m 43sBlocked
run-4800mainMira5m 50sPassed
run-4799release/vegaOmar6m 57sQueued
run-4798codex/ui-polishJules7m 14sReviewing
run-4797docs/mwgNia8m 21sBlocked
run-4796mainMira9m 28sPassed
run-4795release/vegaOmar10m 35sQueued
run-4794codex/ui-polishJules11m 42sReviewing
run-4793docs/mwgNia3m 49sBlocked
'use client';

import { ScrollArea, ScrollBar } from '@/components/nxds/components/scroll-area/scroll-area';

const jobRuns = Array.from({ length: 28 }, (_, i) => ({
  id: `run-${4820 - i}`,
  branch: ['main', 'release/vega', 'codex/ui-polish', 'docs/mwg'][i % 4],
  owner: ['Mira', 'Omar', 'Jules', 'Nia'][i % 4],
  duration: `${3 + (i % 9)}m ${10 + ((i * 7) % 50)}s`,
  status: ['Passed', 'Queued', 'Reviewing', 'Blocked'][i % 4],
}));

export default function ScrollAreaVisibleAffordance() {
  return (
    <ScrollArea
      type="always"
      className="nx:h-64 nx:w-80 nx:rounded-md nx:border-default nx:border-border-default"
    >
      <div className="nx:w-max nx:p-4">
        <div className="nx:grid nx:grid-cols-[112px_160px_96px_96px_96px] nx:gap-x-4 nx:border-b-default nx:border-border-default nx:pb-2 nx:typography-label-small nx:text-muted-foreground">
          <span>Run</span>
          <span>Branch</span>
          <span>Owner</span>
          <span>Duration</span>
          <span>Status</span>
        </div>
        <div className="nx:flex nx:flex-col">
          {jobRuns.map((run) => (
            <div
              key={run.id}
              className="nx:grid nx:grid-cols-[112px_160px_96px_96px_96px] nx:gap-x-4 nx:border-b-default nx:border-border-default nx:py-2 nx:typography-label-default nx:text-foreground nx:last:border-b-0"
            >
              <span className="nx:font-mono nx:typography-label-small">
                {run.id}
              </span>
              <span>{run.branch}</span>
              <span>{run.owner}</span>
              <span>{run.duration}</span>
              <span>{run.status}</span>
            </div>
          ))}
        </div>
      </div>
      <ScrollBar orientation="horizontal" />
    </ScrollArea>
  );
}

In card

Keep a long list inside a Card from stretching the Card.

Builds

  • v1.4.0-canary.40
  • v1.4.0-canary.39
  • v1.4.0-canary.38
  • v1.4.0-canary.37
  • v1.4.0-canary.36
  • v1.4.0-canary.35
  • v1.4.0-canary.34
  • v1.4.0-canary.33
  • v1.4.0-canary.32
  • v1.4.0-canary.31
  • v1.4.0-canary.30
  • v1.4.0-canary.29
  • v1.4.0-canary.28
  • v1.4.0-canary.27
  • v1.4.0-canary.26
  • v1.4.0-canary.25
  • v1.4.0-canary.24
  • v1.4.0-canary.23
  • v1.4.0-canary.22
  • v1.4.0-canary.21
  • v1.4.0-canary.20
  • v1.4.0-canary.19
  • v1.4.0-canary.18
  • v1.4.0-canary.17
  • v1.4.0-canary.16
  • v1.4.0-canary.15
  • v1.4.0-canary.14
  • v1.4.0-canary.13
  • v1.4.0-canary.12
  • v1.4.0-canary.11
  • v1.4.0-canary.10
  • v1.4.0-canary.9
  • v1.4.0-canary.8
  • v1.4.0-canary.7
  • v1.4.0-canary.6
  • v1.4.0-canary.5
  • v1.4.0-canary.4
  • v1.4.0-canary.3
  • v1.4.0-canary.2
  • v1.4.0-canary.1
'use client';

import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from '@/components/nxds/components/card/card';
import { ScrollArea } from '@/components/nxds/components/scroll-area/scroll-area';

const versions = Array.from(
  { length: 40 },
  (_, i) => `v1.4.0-canary.${40 - i}`
);

export default function ScrollAreaInCard() {
  return (
    <Card className="nx:w-80">
      <CardHeader>
        <CardTitle>Builds</CardTitle>
      </CardHeader>
      <CardContent>
        <ScrollArea className="nx:h-48 nx:rounded-md nx:border-default nx:border-border-default">
          <ul className="nx:flex nx:flex-col nx:p-3">
            {versions.map((version) => (
              <li
                key={version}
                className="nx:border-b-default nx:border-border-default nx:py-2 nx:typography-label-default nx:text-container-foreground nx:last:border-b-0"
              >
                {version}
              </li>
            ))}
          </ul>
        </ScrollArea>
      </CardContent>
    </Card>
  );
}

This example also needs: Card.

Props

ScrollArea

ScrollArea adds no props of its own; it accepts the props of the element or primitive it wraps.

ScrollBar

ScrollBar adds no props of its own; it accepts the props of the element or primitive it wraps.

ScrollArea gives a fixed-size region custom scrollbars that look the same on every platform. Use it for panels, lists and drawers with overflowing content. For a full page, let the browser scroll the page.

<script setup lang="ts">
import { ScrollArea } from '@/components/scroll-area';

const notes = [
  'Scroll areas render a consistent custom scrollbar across Chrome, Safari, and Firefox, replacing the platform scrollbar so the chrome looks the same everywhere.',
  'The viewport measures its content and only surfaces a scrollbar on the axis that actually overflows — a wide-but-short block gets a horizontal bar and no vertical one.',
  'Scrollbars fade in on hover by default. Pass type="always" to keep them pinned, or type="scroll" to show them only while the content is moving.',
  'The thumb is sized in proportion to the visible fraction of the content, so a long document gets a short thumb and a barely-overflowing one gets a long thumb.',
  'Keyboard focus can move into the viewport with a visible focus ring, so the scrollable region stays reachable without a pointer.',
];
</script>

<template>
  <ScrollArea
    class="nx:h-48 nx:w-72 nx:rounded-md nx:border-default nx:border-border-default"
  >
    <div
      class="nx:flex nx:flex-col nx:gap-3 nx:p-4 nx:typography-label-default nx:text-foreground"
    >
      <h4 class="nx:font-medium nx:leading-none">Release notes</h4>
      <p v-for="(note, i) in notes" :key="i" class="nx:text-muted-foreground">
        {{ note }}
      </p>
    </div>
  </ScrollArea>
</template>

Installation

  1. Install the following dependencies:

    npm install @vueuse/core@^14.1.0 class-variance-authority@^0.7.1 reka-ui@^2.11.0
    
  2. Copy and paste the following code into your project.

    components/scroll-area/ScrollArea.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      ScrollAreaCorner,
      ScrollAreaRoot,
      type ScrollAreaRootProps,
      ScrollAreaViewport,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    import ScrollBar from './ScrollBar.vue';
    
    export interface ScrollAreaProps extends ScrollAreaRootProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<ScrollAreaProps>();
    
    const delegated = reactiveOmit(props, 'class');
    </script>
    
    <template>
      <ScrollAreaRoot
        v-bind="delegated"
        data-slot="scroll-area"
        :class="cn('nx:relative', props.class)"
      >
        <ScrollAreaViewport
          data-slot="scroll-area-viewport"
          tabindex="0"
          class="nx:size-full nx:rounded-[inherit] nx:focus-visible:outline-2 nx:focus-visible:outline-focus-default"
        >
          <slot />
        </ScrollAreaViewport>
        <ScrollBar />
        <ScrollAreaCorner data-slot="scroll-area-corner" />
      </ScrollAreaRoot>
    </template>
    
    components/scroll-area/ScrollBar.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      ScrollAreaScrollbar,
      type ScrollAreaScrollbarProps,
      ScrollAreaThumb,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    import { scrollBarVariants } from '.';
    
    export interface ScrollBarProps extends ScrollAreaScrollbarProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = withDefaults(defineProps<ScrollBarProps>(), {
      orientation: 'vertical',
    });
    
    const delegated = reactiveOmit(props, 'class');
    </script>
    
    <template>
      <ScrollAreaScrollbar
        v-bind="delegated"
        data-slot="scroll-bar"
        :class="cn(scrollBarVariants({ orientation }), props.class)"
      >
        <ScrollAreaThumb
          data-slot="scroll-bar-thumb"
          class="nx:relative nx:flex-1 nx:rounded-full nx:bg-border-default"
        />
      </ScrollAreaScrollbar>
    </template>
    
    components/scroll-area/index.ts
    import { cva } from 'class-variance-authority';
    
    export { default as ScrollArea, type ScrollAreaProps } from './ScrollArea.vue';
    export { default as ScrollBar, type ScrollBarProps } from './ScrollBar.vue';
    
    export const scrollBarVariants = cva(
      'nx:flex nx:touch-none nx:select-none nx:p-px nx:transition-colors',
      {
        variants: {
          orientation: {
            vertical:
              'nx:h-full nx:w-2.5 nx:border-l-default nx:border-l-transparent',
            horizontal:
              'nx:h-2.5 nx:flex-col nx:border-t-default nx:border-t-transparent',
          },
        },
        defaultVariants: {
          orientation: 'vertical',
        },
      }
    );
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { ScrollArea, ScrollBar } from '@/components/scroll-area';

Examples

Vertical list

Scroll a long list of rows inside a fixed height.

<script setup lang="ts">
import { ScrollArea } from '@/components/scroll-area';

const versions = Array.from(
  { length: 40 },
  (_, i) => `v1.4.0-canary.${40 - i}`
);
</script>

<template>
  <ScrollArea
    class="nx:h-72 nx:w-56 nx:rounded-md nx:border-default nx:border-border-default"
  >
    <div class="nx:p-4">
      <h4
        class="nx:mb-3 nx:typography-label-default nx:leading-none nx:text-foreground"
      >
        Builds
      </h4>
      <ul class="nx:flex nx:flex-col">
        <li
          v-for="version in versions"
          :key="version"
          class="nx:border-b-default nx:border-border-default nx:py-2 nx:typography-label-default nx:text-foreground nx:last:border-b-0"
        >
          {{ version }}
        </li>
      </ul>
    </div>
  </ScrollArea>
</template>

Horizontal row

Add a horizontal ScrollBar to scroll a row of items sideways.

<script setup lang="ts">
import { ScrollArea, ScrollBar } from '@/components/scroll-area';

const artworks = [
  { title: 'Aurora', artist: 'Ornella Binni' },
  { title: 'Tide', artist: 'Tom Byrom' },
  { title: 'Drift', artist: 'Vladimir Malyavko' },
  { title: 'Ember', artist: 'Mara Stein' },
  { title: 'Quartz', artist: 'Liang Wu' },
  { title: 'Meadow', artist: 'Sofia Russo' },
];
</script>

<template>
  <ScrollArea
    class="nx:w-96 nx:rounded-md nx:border-default nx:border-border-default nx:whitespace-nowrap"
  >
    <div class="nx:flex nx:w-max nx:gap-4 nx:p-4">
      <figure
        v-for="artwork in artworks"
        :key="artwork.title"
        class="nx:shrink-0"
      >
        <div
          class="nx:flex nx:h-40 nx:w-32 nx:items-end nx:rounded-md nx:bg-muted nx:p-3"
        >
          <span class="nx:typography-label-default nx:text-foreground">{{
            artwork.title
          }}</span>
        </div>
        <figcaption
          class="nx:pt-2 nx:typography-label-small nx:text-muted-foreground"
        >
          by {{ artwork.artist }}
        </figcaption>
      </figure>
    </div>
    <ScrollBar orientation="horizontal" />
  </ScrollArea>
</template>

Both

Scroll a large grid on both axes with two scrollbars.

<script setup lang="ts">
import { ScrollArea, ScrollBar } from '@/components/scroll-area';
</script>

<template>
  <ScrollArea
    class="nx:h-72 nx:w-96 nx:rounded-md nx:border-default nx:border-border-default"
  >
    <div class="nx:w-max nx:p-4">
      <div v-for="row in 20" :key="row" class="nx:flex nx:gap-2 nx:py-1">
        <div
          v-for="col in 15"
          :key="col"
          class="nx:flex nx:size-12 nx:shrink-0 nx:items-center nx:justify-center nx:rounded-sm nx:bg-muted nx:typography-label-small nx:text-muted-foreground"
        >
          {{ (row - 1) * 15 + col - 1 }}
        </div>
      </div>
    </div>
    <ScrollBar orientation="horizontal" />
  </ScrollArea>
</template>

Visible affordance

Set type="always" so dense panels show their scrollbars without a hover.

<script setup lang="ts">
import { ScrollArea, ScrollBar } from '@/components/scroll-area';

const runs = Array.from({ length: 28 }, (_, i) => ({
  id: `run-${4820 - i}`,
  branch: ['main', 'release/vega', 'codex/ui-polish', 'docs/mwg'][i % 4],
  owner: ['Mira', 'Omar', 'Jules', 'Nia'][i % 4],
  duration: `${3 + (i % 9)}m ${10 + ((i * 7) % 50)}s`,
  status: ['Passed', 'Queued', 'Reviewing', 'Blocked'][i % 4],
}));
</script>

<template>
  <ScrollArea
    type="always"
    class="nx:h-64 nx:w-80 nx:rounded-md nx:border-default nx:border-border-default"
  >
    <div class="nx:w-max nx:p-4">
      <div
        class="nx:grid nx:grid-cols-[112px_160px_96px_96px_96px] nx:gap-x-4 nx:border-b-default nx:border-border-default nx:pb-2 nx:typography-label-small nx:text-muted-foreground"
      >
        <span>Run</span>
        <span>Branch</span>
        <span>Owner</span>
        <span>Duration</span>
        <span>Status</span>
      </div>
      <div class="nx:flex nx:flex-col">
        <div
          v-for="run in runs"
          :key="run.id"
          class="nx:grid nx:grid-cols-[112px_160px_96px_96px_96px] nx:gap-x-4 nx:border-b-default nx:border-border-default nx:py-2 nx:typography-label-default nx:text-foreground nx:last:border-b-0"
        >
          <span class="nx:font-mono nx:typography-label-small">{{
            run.id
          }}</span>
          <span>{{ run.branch }}</span>
          <span>{{ run.owner }}</span>
          <span>{{ run.duration }}</span>
          <span>{{ run.status }}</span>
        </div>
      </div>
    </div>
    <ScrollBar orientation="horizontal" />
  </ScrollArea>
</template>

In card

Keep a long list inside a Card from stretching the Card.

<script setup lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '@/components/card';
import { ScrollArea } from '@/components/scroll-area';

const versions = Array.from(
  { length: 40 },
  (_, i) => `v1.4.0-canary.${40 - i}`
);
</script>

<template>
  <Card class="nx:w-80">
    <CardHeader>
      <CardTitle>Builds</CardTitle>
    </CardHeader>
    <CardContent>
      <ScrollArea
        class="nx:h-48 nx:rounded-md nx:border-default nx:border-border-default"
      >
        <ul class="nx:flex nx:flex-col nx:p-3">
          <li
            v-for="version in versions"
            :key="version"
            class="nx:border-b-default nx:border-border-default nx:py-2 nx:typography-label-default nx:text-container-foreground nx:last:border-b-0"
          >
            {{ version }}
          </li>
        </ul>
      </ScrollArea>
    </CardContent>
  </Card>
</template>

This example also needs: Card.

Props

ScrollArea

ScrollArea adds no props of its own; it accepts the props of the element or primitive it wraps.

ScrollBar

ScrollBar adds no props of its own; it accepts the props of the element or primitive it wraps.