Typing Indicator
Three bouncing dots with a label that signals someone is typing.
Three bouncing dots with a label that signals someone is typing. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
newt-typingimport { TypingIndicator } from "@/registry/bases/newt/ui/typing-indicator"
export default function TypingIndicatorDemo() {
return <TypingIndicator>someone is typing...</TypingIndicator>
}<script setup lang="ts">
import { TypingIndicator } from "~~/registry/bases/newt/ui/typing-indicator"
</script>
<template>
<TypingIndicator>someone is typing...</TypingIndicator>
</template>Installation
pnpm dlx newtui@latest add typing-indicatornpx newtui@latest add typing-indicatoryarn dlx newtui@latest add typing-indicatorbunx newtui@latest add typing-indicatorCopy the source into your project
import * as React from "react"
import { cn } from "@/lib/utils"
const typingKeyframes = `
@keyframes newt-typing-bounce {
0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
30% { transform: translateY(-3px); opacity: 1; }
}`
export interface TypingIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
/** Accessible label announced to screen readers. */
label?: string
}
const TypingIndicator = React.forwardRef<HTMLDivElement, TypingIndicatorProps>(
({ className, label = "Someone is typing", children, ...props }, ref) => (
<div
ref={ref}
role="status"
aria-label={label}
className={cn(
"inline-flex items-center gap-2 text-[13px] text-newt-text-muted",
className
)}
{...props}
>
<style>{typingKeyframes}</style>
<TypingIndicatorDots />
{children}
</div>
)
)
TypingIndicator.displayName = "TypingIndicator"
const TypingIndicatorDots = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("inline-flex gap-[3px]", className)}
aria-hidden="true"
{...props}
>
{[0, 0.15, 0.3].map((delay) => (
<span
key={delay}
className="h-[6px] w-[6px] rounded-full bg-newt-text-muted animate-[newt-typing-bounce_1.2s_ease-in-out_infinite] motion-reduce:animate-none motion-reduce:opacity-80"
style={{ animationDelay: `${delay}s` }}
/>
))}
</div>
))
TypingIndicatorDots.displayName = "TypingIndicatorDots"
export { TypingIndicator, TypingIndicatorDots }<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
import TypingIndicatorDots from "./TypingIndicatorDots.vue"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
/** Accessible label announced to screen readers. */
label?: string
}>(),
{ label: "Someone is typing" }
)
</script>
<template>
<div
role="status"
:aria-label="label"
:class="
cn(
'inline-flex items-center gap-2 text-[13px] text-newt-text-muted',
props.class
)
"
>
<TypingIndicatorDots />
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
const delays = [0, 0.15, 0.3]
</script>
<template>
<div :class="cn('inline-flex gap-[3px]', props.class)" aria-hidden="true">
<span
v-for="delay in delays"
:key="delay"
class="h-[6px] w-[6px] rounded-full bg-newt-text-muted animate-[newt-typing-bounce_1.2s_ease-in-out_infinite] motion-reduce:animate-none motion-reduce:opacity-80"
:style="{ animationDelay: `${delay}s` }"
/>
</div>
</template>
<style>
@keyframes newt-typing-bounce {
0%,
60%,
100% {
transform: translateY(0);
opacity: 0.5;
}
30% {
transform: translateY(-3px);
opacity: 1;
}
}
</style>export { default as TypingIndicator } from "./TypingIndicator.vue"
export { default as TypingIndicatorDots } from "./TypingIndicatorDots.vue"Update the import paths
The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.
Usage
import { TypingIndicator } from "@/components/ui/typing-indicator"
export function Example() {
return <TypingIndicator />
}<script setup lang="ts">
import { TypingIndicator, TypingIndicatorDots } from "@/components/ui/typing-indicator"
</script>
<template>
<TypingIndicator />
</template>API reference
TypingIndicator
label?string | undefined
Accessible label announced to screen readers.
string | undefinedThe registry exports TypingIndicator, TypingIndicatorDots. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.
Tokens
Typing Indicator reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-typing.