Message Bubble

A state-driven speech bubble with incoming and outgoing variants and an optional reaction badge

A state atom whose appearance (hidden, visible) is a pure function of the state prop. The bubble enters from 12 px below at 0.94 scale, fades and scales up to rest. Incoming bubbles align to the left on theme.muted; outgoing bubbles align to the right on theme.primary. An optional reaction emoji badge pops in via the reactionStyle channel.

Installation
$ pnpm dlx shadcn@latest add @remocn/message-bubble

States

MessageBubbleState is:

ts
type MessageBubbleState =
  | "hidden"
  | "visible"

hidden rests at opacity 0, translateY 12 px, and scale 0.94. visible brings the bubble to full opacity, translateY 0, and scale 1.

Smooth transitions

For animated enter, use useMessageBubbleTransition from the copied use-message-bubble-transition.ts file. It reads the frame, interpolates between state presets, and returns a resolved MessageBubbleStyle — pass it to the style prop:

tsx
import { MessageBubble } from "@/components/remocn/message-bubble";
import { useMessageBubbleTransition } from "@/components/remocn/use-message-bubble-transition";
 
export const Scene = () => {
  const style = useMessageBubbleTransition([
    { at: 20, state: "visible", duration: 14 },
    { at: 80, state: "hidden",  duration: 14 },
  ]);
 
  return (
    <div style={{ position: "relative", width: "100%", height: "100%" }}>
      <MessageBubble style={style} variant="incoming">Hey — ready for the demo?</MessageBubble>
    </div>
  );
};

The duration field overrides the file's DEFAULT_DURATION (= 14) for that specific transition. To globally tune timing and easing, edit use-message-bubble-transition.ts directly in your project — that file is yours (shadcn "own your code" philosophy).

style takes precedence over state when both are provided.

Variants

The variant prop sets the color and alignment of the bubble.

ts
type MessageBubbleVariant =
  | "incoming"
  | "outgoing"

incoming uses theme.muted as the background and theme.foreground as text, aligned to the left. outgoing uses theme.primary as the background and theme.primaryForeground as text, aligned to the right.

tsx
<MessageBubble variant="incoming">Hey — ready for the demo?</MessageBubble>
<MessageBubble variant="outgoing">Yep, pushing it live now</MessageBubble>

Reactions

Pass a reaction emoji string to the reaction prop to render an emoji badge anchored to the bottom corner of the bubble. Drive the badge's animated entrance via the reactionStyle channel — a separate style object that controls the badge opacity and scale independently from the bubble body.

tsx
<MessageBubble
  variant="incoming"
  state="visible"
  reaction="🔥"
  reactionStyle={{ opacity: 1, scale: 1 }}
>
  Yep, pushing it live now
</MessageBubble>

When reaction is omitted no badge is rendered.

Props9

PropTypeDefault
state
"hidden" | "visible""hidden"

Current visual state (snap path). State changes snap — no automatic cross-fade.

style
MessageBubbleStyle—

Resolved animated visual (smooth path). Pass an interpolated MessageBubbleStyle from useMessageBubbleTransition. Takes precedence over state when provided.

variant
"incoming" | "outgoing""incoming"

Sets the bubble color and alignment. incoming uses theme.muted and left-aligns; outgoing uses theme.primary and right-aligns.

children
React.ReactNode—

The message text or content rendered inside the bubble.

reaction
string—

Optional emoji string rendered as a badge anchored to the bottom corner of the bubble.

reactionStyle
MessageBubbleStyle—

Animated style for the reaction badge. Controls opacity and scale independently from the bubble body.

maxWidth
number460

Maximum width of the bubble in pixels.

theme
Partial<RemocnTheme>—

Per-component theme override. Merges with the active RemocnUIProvider theme and the mode defaults. Must be concrete oklch/hex/rgb values — not CSS custom properties.

className
string—

Optional className applied to the bubble wrapper element.

  • state"hidden" | "visible"

    Current visual state (snap path). State changes snap — no automatic cross-fade.

    Default"hidden"

  • styleMessageBubbleStyle

    Resolved animated visual (smooth path). Pass an interpolated MessageBubbleStyle from useMessageBubbleTransition. Takes precedence over state when provided.

  • variant"incoming" | "outgoing"

    Sets the bubble color and alignment. incoming uses theme.muted and left-aligns; outgoing uses theme.primary and right-aligns.

    Default"incoming"

  • childrenReact.ReactNode

    The message text or content rendered inside the bubble.

  • reactionstring

    Optional emoji string rendered as a badge anchored to the bottom corner of the bubble.

  • reactionStyleMessageBubbleStyle

    Animated style for the reaction badge. Controls opacity and scale independently from the bubble body.

  • maxWidthnumber

    Maximum width of the bubble in pixels.

    Default460

  • themePartial<RemocnTheme>

    Per-component theme override. Merges with the active RemocnUIProvider theme and the mode defaults. Must be concrete oklch/hex/rgb values — not CSS custom properties.

  • classNamestring

    Optional className applied to the bubble wrapper element.