# Message Bubble
URL: https://remocn.dev/docs/ui/components/message-bubble

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

- Install: `npx shadcn@latest add @remocn/message-bubble`
- Vibe: clean
- Natural length: state-driven

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

```bash
npx shadcn@latest add @remocn/message-bubble
```

> **Note**
>
> Installing `message-bubble` automatically installs the shared `remocn-ui` core (`lib/remocn-ui/`) via `registryDependencies` (`["remocn-ui"]`). You do not need to install it separately. Both `message-bubble` (the renderer) and `use-message-bubble-transition` (the hook) are copied into your project.

## 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.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `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` | `number` | `460` | 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. |

## Use when
- You are hand-timing a single message landing on a beat you control, driving `style` from `useMessageBubbleTransition`.
- A scene needs a themed speech bubble around arbitrary content — text, an image, or a `typing-indicator`.
- The conversation must carry your own `RemocnTheme` tokens rather than a real messenger's palette.

## Don't use when
- You want a whole scripted conversation with a composer, typing beats, and reactions — use `chat-flow`, which schedules bubbles for you.
- The bubble must read as a specific messenger with a tail, timestamp, or tapback — use `telegram-chat-flow` or `imessage-chat-flow`, which own those bubble shapes.
- You need the bouncing dots themselves rather than the container around them — use `typing-indicator`.