GitHub

Pulse dot

A single pulsing dot for chat and assistant UIs, the same minimal cue ChatGPT shows while a reply is generating.

ChatGPT uses this directly in the chat surface: one small dot that pulses at the end of the assistant turn until streamed text appears. PulseDot is the same pattern for your own products: copilots, support bots, and composer footers where a full spinner would feel heavy.

Loading
import { PulseDot } from "@/components/loading-ui/pulse-dot";

export function PulseDotDemo() {

Installation

pnpm dlx shadcn@latest add @loading-ui/pulse-dot

Usage

import { PulseDot } from "@/components/loading-ui/pulse-dot";
<PulseDot />

Customization

The dot is a rounded-full block with bg-current, so color follows inherited foreground and currentColor. Scale sets how large the “breathing” motion feels relative to surrounding copy.

Size

Chat UIs usually keep this tiny (size-2 or size-2.5 beside baseline text), larger in empty states or toolbars.

LoadingLoadingLoadingLoading
import { PulseDot } from "@/components/loading-ui/pulse-dot";

export function PulseDotSize() {

Color

Because the dot is filled with bg-current, its hue follows the element’s text color / currentColor. The preview below uses text-[#…] on purpose so the swatches stay obvious; in real layouts you usually inherit from a parent or use whatever color API matches your theme.

LoadingLoadingLoading
import { PulseDot } from "@/components/loading-ui/pulse-dot";

export function PulseDotColor() {

Duration

Breathing speed uses --duration, defaulting to 1.2s in the component.

LoadingLoadingLoading
import { PulseDot } from "@/components/loading-ui/pulse-dot";

export function PulseDotDuration() {

Examples

Button

Drop-in for “assistant is working” actions without changing button height.

import { Button } from "@/components/ui/button";
import { PulseDot } from "@/components/loading-ui/pulse-dot";

Badge

Thread and model-status chips stay compact because the dot carries the motion alone.

LoadingIn chatLoadingModel activeLoadingLive thread
import { Badge } from "@/components/ui/badge";
import { PulseDot } from "@/components/loading-ui/pulse-dot";

Input group

Mirrors the inline composer pattern: placeholder copy plus a dot at the trailing edge.

LoadingGenerating…
import {
  InputGroup,
  InputGroupAddon,

Tabs

Use a tab panel to separate streaming copy from secondary material (sources, citations).

LoadingSame single-dot idiom you see in ChatGPT while a response is generating: minimal chrome, clear “still working” cue.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { PulseDot } from "@/components/loading-ui/pulse-dot";