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";