GitHub

Original file line numberDiff line numberDiff line change

@@ -1,7 +1,6 @@

11

<script setup lang="ts">

22

import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent } from 'reka-ui'

33

import { useScroll, useResizeObserver } from '@vueuse/core'

4-

import ChatShimmer from './ChatShimmer.vue'

54
65

const props = withDefaults(defineProps<{

76

text?: string

@@ -158,7 +157,7 @@ watch(() => props.text, () => {

158157

/>

159158

<UIcon v-else-if="icon" :name="icon" class="size-4 shrink-0" />

160159
161-

<ChatShimmer v-if="streaming" :text="thinkingMessage" class="truncate" />

160+

<UChatShimmer v-if="streaming" :label="thinkingMessage" class="truncate" />

162161

<span v-else class="truncate">{{ thinkingMessage }}</span>

163162
164163

<UIcon

Original file line numberDiff line numberDiff line change

@@ -1,6 +1,5 @@

11

<script setup lang="ts">

22

import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent } from 'reka-ui'

3-

import ChatShimmer from './ChatShimmer.vue'

43
54

const props = withDefaults(defineProps<{

65

text: string

@@ -88,7 +87,7 @@ const collapsible = computed(() => !!slots.default)

8887

/>

8988
9089

<span class="truncate">

91-

<ChatShimmer v-if="streaming" :text="text" />

90+

<UChatShimmer v-if="streaming" :label="text" />

9291

<template v-else>{{ text }}</template>

9392

<span v-if="suffix" class="text-dimmed ms-1">{{ suffix }}</span>

9493

</span>

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,59 @@

1+

---

2+

title: ChatShimmer

3+

description: Display a text shimmer animation effect.

4+

category: chat

5+

links:

6+

- label: GitHub

7+

icon: i-simple-icons-github

8+

to: https://github.com/nuxt/ui/blob/v4/src/runtime/components/ChatShimmer.vue

9+

navigation.badge: Soon

10+

---

11+
12+

## Usage

13+
14+

The ChatShimmer component renders an element with an animated shimmer gradient over text, commonly used to indicate streaming or loading states in chat interfaces.

15+
16+

::component-code

17+

---

18+

props:

19+

label: 'Thinking...'

20+

---

21+

::

22+
23+

### Duration

24+
25+

Use the `duration` prop to control the animation speed in seconds.

26+
27+

::component-code

28+

---

29+

props:

30+

label: 'Thinking...'

31+

duration: 4

32+

---

33+

::

34+
35+

### Spread

36+
37+

Use the `spread` prop to control the width of the shimmer highlight. The actual spread is computed as `label.length * spread` in pixels.

38+
39+

::component-code

40+

---

41+

props:

42+

label: 'Thinking...'

43+

spread: 5

44+

---

45+

::

46+
47+

## API

48+
49+

### Props

50+
51+

:component-props

52+
53+

## Theme

54+
55+

:component-theme

56+
57+

## Changelog

58+
59+

:component-changelog

Original file line numberDiff line numberDiff line change

@@ -13,6 +13,7 @@ const components = [

1313

'calendar',

1414

'card',

1515

'carousel',

16+

'chat-shimmer',

1617

'changelog-version',

1718

'changelog-versions',

1819

'checkbox-group',

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,9 @@

1+

<template>

2+

<div class="flex flex-col items-start gap-4">

3+

<UChatShimmer label="Thinking..." />

4+
5+

<UChatShimmer label="Loading content..." :duration="4" />

6+
7+

<UChatShimmer label="Generating response..." :spread="5" />

8+

</div>

9+

</template>

Original file line numberDiff line numberDiff line change

@@ -4,7 +4,7 @@ import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'

44

import { gateway } from '@ai-sdk/gateway'

55
66

const webSearchTool = anthropic.tools.webSearch_20250305({

7-

maxUses: 5

7+

maxUses: 1

88

})

99
1010

export default defineEventHandler(async (event) => {

@@ -25,8 +25,5 @@ export default defineEventHandler(async (event) => {

2525

effort: 'low'

2626

} satisfies AnthropicLanguageModelOptions

2727

}

28-

}).toUIMessageStreamResponse({

29-

sendReasoning: true,

30-

sendSources: true

31-

})

28+

}).toUIMessageStreamResponse()

3229

})

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,67 @@

1+

<script lang="ts">

2+

import type { AppConfig } from '@nuxt/schema'

3+

import theme from '#build/ui/chat-shimmer'

4+

import type { ComponentConfig } from '../types/tv'

5+
6+

type ChatShimmer = ComponentConfig<typeof theme, AppConfig, 'chatShimmer'>

7+
8+

export interface ChatShimmerProps {

9+

/**

10+

* The element or component this component should render as.

11+

* @defaultValue 'span'

12+

*/

13+

as?: any

14+

/**

15+

* The label to display with the shimmer effect.

16+

*/

17+

label: string

18+

/**

19+

* The duration of the shimmer animation in seconds.

20+

* @defaultValue 2

21+

*/

22+

duration?: number

23+

/**

24+

* The spread multiplier for the shimmer highlight. The actual spread is computed as `label.length * spread` in pixels.

25+

* @defaultValue 2

26+

*/

27+

spread?: number

28+

class?: any

29+

ui?: ChatShimmer['slots']

30+

}

31+

</script>

32+
33+

<script setup lang="ts">

34+

import { computed } from 'vue'

35+

import { Primitive } from 'reka-ui'

36+

import { useAppConfig } from '#imports'

37+

import { useComponentUI } from '../composables/useComponentUI'

38+

import { tv } from '../utils/tv'

39+
40+

const props = withDefaults(defineProps<ChatShimmerProps>(), {

41+

as: 'span',

42+

duration: 2,

43+

spread: 2

44+

})

45+
46+

const appConfig = useAppConfig() as ChatShimmer['AppConfig']

47+

const uiProp = useComponentUI('chatShimmer', props)

48+
49+

// eslint-disable-next-line vue/no-dupe-keys

50+

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.chatShimmer || {}) }))

51+
52+

const spread = computed(() => props.label.length * props.spread)

53+

</script>

54+
55+

<template>

56+

<Primitive

57+

:as="as"

58+

:style="{

59+

'--spread': `${spread}px`,

60+

'--duration': `${duration}s`

61+

}"

62+

data-slot="base"

63+

:class="ui({ class: [uiProp?.base, props.class] })"

64+

>

65+

{{ label }}

66+

</Primitive>

67+

</template>

Read the original on github.com ↗