Scroll Area
A scrollable region with styled scrollbars.
- v1.0.0
- SSR
- Zoneless
- 0.6 kB gzip
import { Component } from '@angular/core';
import { ScrollArea } from '@/ui/scroll-area/scroll-area';
import { Separator } from '@/ui/separator/separator';
/** A list of tags that scrolls vertically in a fixed height. */
@Component({
selector: 'example-scroll-area-demo',
imports: [ScrollArea, Separator],
template: `
<div ndScrollArea class="h-72 w-48 rounded-md border" aria-label="Tags">
<div class="p-4">
<h2 class="mb-4 text-sm leading-none font-medium">Tags</h2>
@for (tag of tags; track tag; let last = $last) {
<div class="text-sm">{{ tag }}</div>
@if (!last) {
<div ndSeparator decorative class="my-2"></div>
}
}
</div>
</div>
`,
})
export class ScrollAreaDemoExample {
protected readonly tags = Array.from(
{ length: 30 },
(_, index) => `v1.2.0-beta.${30 - index}`,
);
}
Scroll Area is a region that scrolls inside a fixed size, with thin
scrollbars in the theme's border color. It uses the browser's own
scrolling, so touch, the mouse wheel and the keyboard all work, and it
can take focus so keyboard users can scroll it with the arrow keys. Choose
vertical, horizontal or both.
Installation
npx @ndop/cli add scroll-areapnpm dlx @ndop/cli add scroll-areayarn dlx @ndop/cli add scroll-areabunx @ndop/cli add scroll-areaInstall the dependencies
npm install @ndop/corepnpm add @ndop/coreyarn add @ndop/corebun add @ndop/coreCopy the code into your project
At the paths your
ndop.jsonaliases point to; these use the defaults.src/app/ui/scroll-area/scroll-area.tsimport { computed, Directive, input } from '@angular/core'; import { cn } from '@/lib/utils'; /** Which way a {@link ScrollArea} scrolls. */ export type ScrollAreaOrientation = 'vertical' | 'horizontal' | 'both'; /** * A scrollable region with thin, themed scrollbars, using the browser's * own scrolling (ADR-0021): touch, wheel and keyboard scrolling all work, * and it can take focus, so keyboard users can scroll it. Give it a size * and, when it holds more than a list, a name with `aria-label`. * * @example * ```html * <div ndScrollArea class="h-72 w-48 rounded-md border" aria-label="Tags">…</div> * ``` */ @Directive({ selector: '[ndScrollArea]', host: { 'data-slot': 'scroll-area', tabindex: '0', '[attr.data-orientation]': 'orientation()', '[class]': 'classes()', }, }) export class ScrollArea { /** Which way it scrolls. */ readonly orientation = input<ScrollAreaOrientation>('vertical'); /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'relative', 'size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1', '[scrollbar-color:var(--border)_transparent] [scrollbar-width:thin]', this.orientation() === 'horizontal' ? 'overflow-x-auto overflow-y-hidden' : this.orientation() === 'both' ? 'overflow-auto' : 'overflow-x-hidden overflow-y-auto', this.class(), ), ); }src/app/lib/utils.ts/** * Class helpers used by every styled item. They come from `@ndop/core`; * this file is yours, so it is the place to customize them, for example by * wrapping `cn` with an extended tailwind-merge configuration for your own * utilities. * * @packageDocumentation */ export { cn, variants, type ClassValue, type VariantOf } from '@ndop/core';Update the import paths to match your project
The files import each other through the
@/uiand@/libaliases.
Usage
import { ScrollArea } from '@/ui/scroll-area/scroll-area';
import { Separator } from '@/ui/separator/separator';<div ndScrollArea class="h-72 w-48 rounded-md border" aria-label="Tags">
<div class="p-4">
<h2 class="mb-4 text-sm leading-none font-medium">Tags</h2>
@for (tag of tags; track tag; let last = $last) {
<div class="text-sm">{{ tag }}</div>
@if (!last) {
<div ndSeparator decorative class="my-2"></div>
}
}
</div>
</div>Examples
Scroll Area horizontal
A row of artworks that scrolls sideways.
import { Component } from '@angular/core';
import { AspectRatio } from '@/ui/aspect-ratio/aspect-ratio';
import { ScrollArea } from '@/ui/scroll-area/scroll-area';
/** A row of artworks that scrolls sideways. */
@Component({
selector: 'example-scroll-area-horizontal',
imports: [AspectRatio, ScrollArea],
template: `
<div
ndScrollArea
orientation="horizontal"
class="w-full max-w-md rounded-md border whitespace-nowrap"
aria-label="Artworks"
>
<ul class="flex w-max gap-4 p-4">
@for (artwork of artworks; track artwork.title) {
<li class="w-36 shrink-0">
<figure>
<div
ndAspectRatio
[ratio]="3 / 4"
class="rounded-md bg-muted"
></div>
<figcaption class="pt-2 text-xs text-muted-foreground">
By
<span class="font-semibold text-foreground">{{
artwork.artist
}}</span>
</figcaption>
</figure>
</li>
}
</ul>
</div>
`,
})
export class ScrollAreaHorizontalExample {
protected readonly artworks = [
{ title: 'Ripple', artist: 'Ornella Binni' },
{ title: 'Stillness', artist: 'Tom Byrom' },
{ title: 'Drift', artist: 'Vladimir Malyavko' },
{ title: 'Haze', artist: 'Ahmed Saeed' },
];
}
Accessibility
Scroll Area has no keyboard interaction or focus handling of its own; it keeps the semantics of the element it's on.
API reference
ScrollArea
[ndScrollArea]A scrollable region with thin, themed scrollbars, using the browser's
own scrolling (ADR-0021): touch, wheel and keyboard scrolling all work,
and it can take focus, so keyboard users can scroll it. Give it a size
and, when it holds more than a list, a name with aria-label.
| ScrollArea binding | Type | Default |
|---|---|---|
[orientation]Which way it scrolls. | 'horizontal' | 'vertical' | 'both' | 'vertical' |
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | scroll-area |
data-orientation | — |
Agent notes
Do
- Give it a height (or width, when horizontal), such as
class="h-72". - Name it with
aria-labelwhen it holds a section of the page rather than a single list. - Keep focusable controls inside it reachable; scrolling follows focus.
Don't
- Don't nest scroll areas in the same direction.
- Don't use it to hide content that should be visible; let the page scroll.