Installation
Complete project setup first. Your configuration
must select vue/reka and include your Design System ID.
pnpm exec frontfriend download message-scroller -f vue --primitive rekaReview dependencies reported by the downloader and keep your source changes in version control.
Usage
<script setup lang="ts">
import { computed } from "vue";
import { ref } from "vue";
import { Button } from "@/components/ui/button";
import { MessageScrollerProvider } from "@/components/ui/message-scroller";
import { MessageScroller } from "@/components/ui/message-scroller";
import { MessageScrollerViewport } from "@/components/ui/message-scroller";
import { MessageScrollerContent } from "@/components/ui/message-scroller";
import { MessageScrollerItem } from "@/components/ui/message-scroller";
import { Message } from "@/components/ui/message";
import { MessageAvatar } from "@/components/ui/message";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { MessageScrollerButton } from "@/components/ui/message-scroller";
type PreviewProps = {
defaultScrollPosition?: "start" | "end" | "last-anchor";
autoScroll?: boolean;
height?: number;
streaming?: boolean;
history?: boolean;
};
const reply =
"Use the component source for your selected target. The design system supplies the styles. Keep message IDs stable when you load history or update a reply. ";
const args = {
"autoScroll": true,
"defaultScrollPosition": "end",
"height": 300,
"streaming": undefined,
"history": undefined,
} satisfies Partial<
InstanceType<typeof MessageScroller> extends { $props: infer Props }
? Props
: {}
> &
Record<string, unknown> & {
defaultScrollPosition?: "start" | "end" | "last-anchor";
};
const initialPosition = computed(
() => (args as PreviewProps).defaultScrollPosition ?? "end",
);
const messages = ref(
Array.from({ length: 12 }, (_, index) => ({
id: `message-${index}`,
user: index % 2 === 0,
text:
index % 2 === 0
? `Question ${index / 2 + 1}: How do I build this conversation?`
: reply.repeat(index === 11 ? 10 : 2),
})),
);
const updates = ref(0);
function append() {
messages.value[messages.value.length - 1]!.text += reply;
updates.value += 1;
}
function prepend() {
messages.value.unshift({
id: `history-${messages.value.length}`,
user: false,
text: "Earlier message. " + reply.repeat(2),
});
updates.value += 1;
}
</script>
<template>
<div
class="flex w-[420px] max-w-[calc(100vw-2rem)] flex-col gap-3 text-neutral-strong"
>
<div
v-if="args.streaming || args.history"
class="flex flex-wrap items-center gap-2"
>
<Button
type="button"
variant="outline"
@click="args.streaming ? append() : prepend()"
>{{
args.streaming ? "Append response" : "Load earlier messages"
}}</Button
>
<span role="status" class="text-sm text-neutral-mid"
>Updates: {{ updates }}</span
>
</div>
<div
class="w-full overflow-hidden rounded-xl border border-neutral-subtle"
:style="{ height: (args.height || 420) + 'px' }"
>
<MessageScrollerProvider
:default-scroll-position="initialPosition"
:auto-scroll="args.autoScroll || false"
>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
<MessageScrollerItem
v-for="message in messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.user"
>
<div class="px-4 py-2">
<Message :align="message.user ? 'end' : undefined">
<MessageAvatar
><Avatar
><AvatarFallback>{{
message.user ? "ME" : "AI"
}}</AvatarFallback></Avatar
></MessageAvatar
>
<MessageContent
><Bubble :variant="message.user ? undefined : 'muted'"
><BubbleContent>{{
message.text
}}</BubbleContent></Bubble
></MessageContent
>
</Message>
</div>
</MessageScrollerItem>
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton direction="end" />
<MessageScrollerButton direction="start" />
</MessageScroller>
</MessageScrollerProvider>
</div>
</div>
</template> The public useMessageScroller, useMessageScrollerScrollable and useMessageScrollerVisibility hooks provide scroll commands, scroll-edge state and visible message IDs. Call them within the provider. Vue exposes the same three composables, with reactive refs for state and visibility.
Import useMessageScroller, useMessageScrollerScrollable and useMessageScrollerVisibility from the component barrel and call them in setup within the provider. The first composable returns scrollToStart, scrollToEnd and scrollToMessage. The other two return reactive refs with scroll-edge state and visible message IDs.
Examples
Default
<script setup lang="ts">
import { computed } from "vue";
import { ref } from "vue";
import { Button } from "@/components/ui/button";
import { MessageScrollerProvider } from "@/components/ui/message-scroller";
import { MessageScroller } from "@/components/ui/message-scroller";
import { MessageScrollerViewport } from "@/components/ui/message-scroller";
import { MessageScrollerContent } from "@/components/ui/message-scroller";
import { MessageScrollerItem } from "@/components/ui/message-scroller";
import { Message } from "@/components/ui/message";
import { MessageAvatar } from "@/components/ui/message";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { MessageScrollerButton } from "@/components/ui/message-scroller";
type PreviewProps = {
defaultScrollPosition?: "start" | "end" | "last-anchor";
autoScroll?: boolean;
height?: number;
streaming?: boolean;
history?: boolean;
};
const reply =
"Use the component source for your selected target. The design system supplies the styles. Keep message IDs stable when you load history or update a reply. ";
const args = {
"streaming": undefined,
"history": undefined,
"height": undefined,
"autoScroll": undefined,
} satisfies Partial<
InstanceType<typeof MessageScroller> extends { $props: infer Props }
? Props
: {}
> &
Record<string, unknown>;
const initialPosition = computed(
() => (args as PreviewProps).defaultScrollPosition ?? "end",
);
const messages = ref(
Array.from({ length: 12 }, (_, index) => ({
id: `message-${index}`,
user: index % 2 === 0,
text:
index % 2 === 0
? `Question ${index / 2 + 1}: How do I build this conversation?`
: reply.repeat(index === 11 ? 10 : 2),
})),
);
const updates = ref(0);
function append() {
messages.value[messages.value.length - 1]!.text += reply;
updates.value += 1;
}
function prepend() {
messages.value.unshift({
id: `history-${messages.value.length}`,
user: false,
text: "Earlier message. " + reply.repeat(2),
});
updates.value += 1;
}
</script>
<template>
<div
class="flex w-[420px] max-w-[calc(100vw-2rem)] flex-col gap-3 text-neutral-strong"
>
<div
v-if="args.streaming || args.history"
class="flex flex-wrap items-center gap-2"
>
<Button
type="button"
variant="outline"
@click="args.streaming ? append() : prepend()"
>{{
args.streaming ? "Append response" : "Load earlier messages"
}}</Button
>
<span role="status" class="text-sm text-neutral-mid"
>Updates: {{ updates }}</span
>
</div>
<div
class="w-full overflow-hidden rounded-xl border border-neutral-subtle"
:style="{ height: (args.height || 420) + 'px' }"
>
<MessageScrollerProvider
:default-scroll-position="initialPosition"
:auto-scroll="args.autoScroll || false"
>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
<MessageScrollerItem
v-for="message in messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.user"
>
<div class="px-4 py-2">
<Message :align="message.user ? 'end' : undefined">
<MessageAvatar
><Avatar
><AvatarFallback>{{
message.user ? "ME" : "AI"
}}</AvatarFallback></Avatar
></MessageAvatar
>
<MessageContent
><Bubble :variant="message.user ? undefined : 'muted'"
><BubbleContent>{{
message.text
}}</BubbleContent></Bubble
></MessageContent
>
</Message>
</div>
</MessageScrollerItem>
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton direction="end" />
<MessageScrollerButton direction="start" />
</MessageScroller>
</MessageScrollerProvider>
</div>
</div>
</template> Initial Start
<script setup lang="ts">
import { computed } from "vue";
import { ref } from "vue";
import { Button } from "@/components/ui/button";
import { MessageScrollerProvider } from "@/components/ui/message-scroller";
import { MessageScroller } from "@/components/ui/message-scroller";
import { MessageScrollerViewport } from "@/components/ui/message-scroller";
import { MessageScrollerContent } from "@/components/ui/message-scroller";
import { MessageScrollerItem } from "@/components/ui/message-scroller";
import { Message } from "@/components/ui/message";
import { MessageAvatar } from "@/components/ui/message";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { MessageScrollerButton } from "@/components/ui/message-scroller";
type PreviewProps = {
defaultScrollPosition?: "start" | "end" | "last-anchor";
autoScroll?: boolean;
height?: number;
streaming?: boolean;
history?: boolean;
};
const reply =
"Use the component source for your selected target. The design system supplies the styles. Keep message IDs stable when you load history or update a reply. ";
const args = {
"defaultScrollPosition": "start",
"streaming": undefined,
"history": undefined,
"height": undefined,
"autoScroll": undefined,
} satisfies Partial<
InstanceType<typeof MessageScroller> extends { $props: infer Props }
? Props
: {}
> &
Record<string, unknown>;
const initialPosition = computed(
() => (args as PreviewProps).defaultScrollPosition ?? "end",
);
const messages = ref(
Array.from({ length: 12 }, (_, index) => ({
id: `message-${index}`,
user: index % 2 === 0,
text:
index % 2 === 0
? `Question ${index / 2 + 1}: How do I build this conversation?`
: reply.repeat(index === 11 ? 10 : 2),
})),
);
const updates = ref(0);
function append() {
messages.value[messages.value.length - 1]!.text += reply;
updates.value += 1;
}
function prepend() {
messages.value.unshift({
id: `history-${messages.value.length}`,
user: false,
text: "Earlier message. " + reply.repeat(2),
});
updates.value += 1;
}
</script>
<template>
<div
class="flex w-[420px] max-w-[calc(100vw-2rem)] flex-col gap-3 text-neutral-strong"
>
<div
v-if="args.streaming || args.history"
class="flex flex-wrap items-center gap-2"
>
<Button
type="button"
variant="outline"
@click="args.streaming ? append() : prepend()"
>{{
args.streaming ? "Append response" : "Load earlier messages"
}}</Button
>
<span role="status" class="text-sm text-neutral-mid"
>Updates: {{ updates }}</span
>
</div>
<div
class="w-full overflow-hidden rounded-xl border border-neutral-subtle"
:style="{ height: (args.height || 420) + 'px' }"
>
<MessageScrollerProvider
:default-scroll-position="initialPosition"
:auto-scroll="args.autoScroll || false"
>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
<MessageScrollerItem
v-for="message in messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.user"
>
<div class="px-4 py-2">
<Message :align="message.user ? 'end' : undefined">
<MessageAvatar
><Avatar
><AvatarFallback>{{
message.user ? "ME" : "AI"
}}</AvatarFallback></Avatar
></MessageAvatar
>
<MessageContent
><Bubble :variant="message.user ? undefined : 'muted'"
><BubbleContent>{{
message.text
}}</BubbleContent></Bubble
></MessageContent
>
</Message>
</div>
</MessageScrollerItem>
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton direction="end" />
<MessageScrollerButton direction="start" />
</MessageScroller>
</MessageScrollerProvider>
</div>
</div>
</template> Last Anchor
<script setup lang="ts">
import { computed } from "vue";
import { ref } from "vue";
import { Button } from "@/components/ui/button";
import { MessageScrollerProvider } from "@/components/ui/message-scroller";
import { MessageScroller } from "@/components/ui/message-scroller";
import { MessageScrollerViewport } from "@/components/ui/message-scroller";
import { MessageScrollerContent } from "@/components/ui/message-scroller";
import { MessageScrollerItem } from "@/components/ui/message-scroller";
import { Message } from "@/components/ui/message";
import { MessageAvatar } from "@/components/ui/message";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { MessageScrollerButton } from "@/components/ui/message-scroller";
type PreviewProps = {
defaultScrollPosition?: "start" | "end" | "last-anchor";
autoScroll?: boolean;
height?: number;
streaming?: boolean;
history?: boolean;
};
const reply =
"Use the component source for your selected target. The design system supplies the styles. Keep message IDs stable when you load history or update a reply. ";
const args = {
"defaultScrollPosition": "last-anchor",
"streaming": undefined,
"history": undefined,
"height": undefined,
"autoScroll": undefined,
} satisfies Partial<
InstanceType<typeof MessageScroller> extends { $props: infer Props }
? Props
: {}
> &
Record<string, unknown>;
const initialPosition = computed(
() => (args as PreviewProps).defaultScrollPosition ?? "end",
);
const messages = ref(
Array.from({ length: 12 }, (_, index) => ({
id: `message-${index}`,
user: index % 2 === 0,
text:
index % 2 === 0
? `Question ${index / 2 + 1}: How do I build this conversation?`
: reply.repeat(index === 11 ? 10 : 2),
})),
);
const updates = ref(0);
function append() {
messages.value[messages.value.length - 1]!.text += reply;
updates.value += 1;
}
function prepend() {
messages.value.unshift({
id: `history-${messages.value.length}`,
user: false,
text: "Earlier message. " + reply.repeat(2),
});
updates.value += 1;
}
</script>
<template>
<div
class="flex w-[420px] max-w-[calc(100vw-2rem)] flex-col gap-3 text-neutral-strong"
>
<div
v-if="args.streaming || args.history"
class="flex flex-wrap items-center gap-2"
>
<Button
type="button"
variant="outline"
@click="args.streaming ? append() : prepend()"
>{{
args.streaming ? "Append response" : "Load earlier messages"
}}</Button
>
<span role="status" class="text-sm text-neutral-mid"
>Updates: {{ updates }}</span
>
</div>
<div
class="w-full overflow-hidden rounded-xl border border-neutral-subtle"
:style="{ height: (args.height || 420) + 'px' }"
>
<MessageScrollerProvider
:default-scroll-position="initialPosition"
:auto-scroll="args.autoScroll || false"
>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
<MessageScrollerItem
v-for="message in messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.user"
>
<div class="px-4 py-2">
<Message :align="message.user ? 'end' : undefined">
<MessageAvatar
><Avatar
><AvatarFallback>{{
message.user ? "ME" : "AI"
}}</AvatarFallback></Avatar
></MessageAvatar
>
<MessageContent
><Bubble :variant="message.user ? undefined : 'muted'"
><BubbleContent>{{
message.text
}}</BubbleContent></Bubble
></MessageContent
>
</Message>
</div>
</MessageScrollerItem>
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton direction="end" />
<MessageScrollerButton direction="start" />
</MessageScroller>
</MessageScrollerProvider>
</div>
</div>
</template> Streaming
<script setup lang="ts">
import { computed } from "vue";
import { ref } from "vue";
import { Button } from "@/components/ui/button";
import { MessageScrollerProvider } from "@/components/ui/message-scroller";
import { MessageScroller } from "@/components/ui/message-scroller";
import { MessageScrollerViewport } from "@/components/ui/message-scroller";
import { MessageScrollerContent } from "@/components/ui/message-scroller";
import { MessageScrollerItem } from "@/components/ui/message-scroller";
import { Message } from "@/components/ui/message";
import { MessageAvatar } from "@/components/ui/message";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { MessageScrollerButton } from "@/components/ui/message-scroller";
type PreviewProps = {
defaultScrollPosition?: "start" | "end" | "last-anchor";
autoScroll?: boolean;
height?: number;
streaming?: boolean;
history?: boolean;
};
const reply =
"Use the component source for your selected target. The design system supplies the styles. Keep message IDs stable when you load history or update a reply. ";
const args = {
"autoScroll": true,
"streaming": true,
"history": undefined,
"height": undefined,
} satisfies Partial<
InstanceType<typeof MessageScroller> extends { $props: infer Props }
? Props
: {}
> &
Record<string, unknown>;
const initialPosition = computed(
() => (args as PreviewProps).defaultScrollPosition ?? "end",
);
const messages = ref(
Array.from({ length: 12 }, (_, index) => ({
id: `message-${index}`,
user: index % 2 === 0,
text:
index % 2 === 0
? `Question ${index / 2 + 1}: How do I build this conversation?`
: reply.repeat(index === 11 ? 10 : 2),
})),
);
const updates = ref(0);
function append() {
messages.value[messages.value.length - 1]!.text += reply;
updates.value += 1;
}
function prepend() {
messages.value.unshift({
id: `history-${messages.value.length}`,
user: false,
text: "Earlier message. " + reply.repeat(2),
});
updates.value += 1;
}
</script>
<template>
<div
class="flex w-[420px] max-w-[calc(100vw-2rem)] flex-col gap-3 text-neutral-strong"
>
<div
v-if="args.streaming || args.history"
class="flex flex-wrap items-center gap-2"
>
<Button
type="button"
variant="outline"
@click="args.streaming ? append() : prepend()"
>{{
args.streaming ? "Append response" : "Load earlier messages"
}}</Button
>
<span role="status" class="text-sm text-neutral-mid"
>Updates: {{ updates }}</span
>
</div>
<div
class="w-full overflow-hidden rounded-xl border border-neutral-subtle"
:style="{ height: (args.height || 420) + 'px' }"
>
<MessageScrollerProvider
:default-scroll-position="initialPosition"
:auto-scroll="args.autoScroll || false"
>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
<MessageScrollerItem
v-for="message in messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.user"
>
<div class="px-4 py-2">
<Message :align="message.user ? 'end' : undefined">
<MessageAvatar
><Avatar
><AvatarFallback>{{
message.user ? "ME" : "AI"
}}</AvatarFallback></Avatar
></MessageAvatar
>
<MessageContent
><Bubble :variant="message.user ? undefined : 'muted'"
><BubbleContent>{{
message.text
}}</BubbleContent></Bubble
></MessageContent
>
</Message>
</div>
</MessageScrollerItem>
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton direction="end" />
<MessageScrollerButton direction="start" />
</MessageScroller>
</MessageScrollerProvider>
</div>
</div>
</template> Prepended History
<script setup lang="ts">
import { computed } from "vue";
import { ref } from "vue";
import { Button } from "@/components/ui/button";
import { MessageScrollerProvider } from "@/components/ui/message-scroller";
import { MessageScroller } from "@/components/ui/message-scroller";
import { MessageScrollerViewport } from "@/components/ui/message-scroller";
import { MessageScrollerContent } from "@/components/ui/message-scroller";
import { MessageScrollerItem } from "@/components/ui/message-scroller";
import { Message } from "@/components/ui/message";
import { MessageAvatar } from "@/components/ui/message";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { MessageScrollerButton } from "@/components/ui/message-scroller";
type PreviewProps = {
defaultScrollPosition?: "start" | "end" | "last-anchor";
autoScroll?: boolean;
height?: number;
streaming?: boolean;
history?: boolean;
};
const reply =
"Use the component source for your selected target. The design system supplies the styles. Keep message IDs stable when you load history or update a reply. ";
const args = {
"defaultScrollPosition": "start",
"history": true,
"streaming": undefined,
"height": undefined,
"autoScroll": undefined,
} satisfies Partial<
InstanceType<typeof MessageScroller> extends { $props: infer Props }
? Props
: {}
> &
Record<string, unknown>;
const initialPosition = computed(
() => (args as PreviewProps).defaultScrollPosition ?? "end",
);
const messages = ref(
Array.from({ length: 12 }, (_, index) => ({
id: `message-${index}`,
user: index % 2 === 0,
text:
index % 2 === 0
? `Question ${index / 2 + 1}: How do I build this conversation?`
: reply.repeat(index === 11 ? 10 : 2),
})),
);
const updates = ref(0);
function append() {
messages.value[messages.value.length - 1]!.text += reply;
updates.value += 1;
}
function prepend() {
messages.value.unshift({
id: `history-${messages.value.length}`,
user: false,
text: "Earlier message. " + reply.repeat(2),
});
updates.value += 1;
}
</script>
<template>
<div
class="flex w-[420px] max-w-[calc(100vw-2rem)] flex-col gap-3 text-neutral-strong"
>
<div
v-if="args.streaming || args.history"
class="flex flex-wrap items-center gap-2"
>
<Button
type="button"
variant="outline"
@click="args.streaming ? append() : prepend()"
>{{
args.streaming ? "Append response" : "Load earlier messages"
}}</Button
>
<span role="status" class="text-sm text-neutral-mid"
>Updates: {{ updates }}</span
>
</div>
<div
class="w-full overflow-hidden rounded-xl border border-neutral-subtle"
:style="{ height: (args.height || 420) + 'px' }"
>
<MessageScrollerProvider
:default-scroll-position="initialPosition"
:auto-scroll="args.autoScroll || false"
>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
<MessageScrollerItem
v-for="message in messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.user"
>
<div class="px-4 py-2">
<Message :align="message.user ? 'end' : undefined">
<MessageAvatar
><Avatar
><AvatarFallback>{{
message.user ? "ME" : "AI"
}}</AvatarFallback></Avatar
></MessageAvatar
>
<MessageContent
><Bubble :variant="message.user ? undefined : 'muted'"
><BubbleContent>{{
message.text
}}</BubbleContent></Bubble
></MessageContent
>
</Message>
</div>
</MessageScrollerItem>
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton direction="end" />
<MessageScrollerButton direction="start" />
</MessageScroller>
</MessageScrollerProvider>
</div>
</div>
</template> API reference
Frontfriend-owned props. Native HTML and inherited primitive props remain documented by Reka UI.
MessageScroller
| Prop | Type | Default | Description |
|---|---|---|---|
class | HTMLAttributes["class"] | — |
MessageScrollerButton
| Prop | Type | Default | Description |
|---|---|---|---|
behavior | ScrollBehavior | — | |
class | HTMLAttributes["class"] | — | |
direction | MessageScrollerButtonDirection | — | |
size | ButtonVariants["size"] | — | |
variant | ButtonVariants["variant"] | — |
MessageScrollerContent
| Prop | Type | Default | Description |
|---|---|---|---|
class | HTMLAttributes["class"] | — | |
spacerClass | HTMLAttributes["class"] | — |
MessageScrollerItem
| Prop | Type | Default | Description |
|---|---|---|---|
class | HTMLAttributes["class"] | — | |
messageId | string | — | |
scrollAnchor | boolean | — |
MessageScrollerViewport
| Prop | Type | Default | Description |
|---|---|---|---|
class | HTMLAttributes["class"] | — | |
preserveScrollOnPrepend | boolean | — |
Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.