Components/Bubble

Bubble

Seven chat surfaces on one axis, from a solid primary send to no surface at all, with the reaction pill already drawn into every one of them and switched off.

Bubble · Components frameLive in the browser · drawn to match the file
Variant
Reactions
Sounds good - I'll send the draft over this afternoon.
Variant=Default · base.primary280 × 66 in the file
reactions are a hidden layer

Every variant carries the reaction pill already, switched off. Showing one does not move the bubble: the pill hangs off the bottom edge with a 3px base.card ring so it reads as sitting on top of whatever is behind it. That is why Variant is the only axis.

Tinted needed a new token

It is the one surface the kit had no colour for: lighter than base.secondary, heavier than base.muted. base.bubble-tinted was added for it, #E8E8E8 in light and #2E2E2E in dark, so it themes with everything else rather than sitting on a literal.

Ghost has no surface

No fill, no padding, no radius: 280 × 46 against everyone else's 66. It is for text that belongs in the thread without being a message, which in the usage frame is the timestamp under a failed send.

The second component

Bubble Group, and where the sides come from

420 wide, a column with 8 of gap, holding three Bubble instances. It has no axes: which side a message sits on is set per bubble inside the group rather than by a property on the group, because a real thread alternates and a two-value axis could not express that.

Bubble Group · Components frameLive in the browser · drawn to match the file
Morning - did the revised deck land in your inbox?
It did, thanks. Reviewing it now.
No rush. Ping me if the pricing slide needs another pass.
420 × 214 in the file
The usage frame

A thread, a reaction, and a send that failed

620 by 803, three sections. The last one is the reason Ghost exists: a Destructive bubble for the error, with the timestamp under it as a Ghost bubble rather than as loose text.

Section
Morning - did the revised deck land in your inbox?
It did, thanks. Reviewing it now.
No rush. Ping me if the pricing slide needs another pass.
In the file

Two components, seven variants

Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.

BubbleVariantDefault · Secondary · Muted · Tinted · Outline · Ghost · Destructive - 280 wide, 66 tall except Ghost at 467
Bubble Groupnone420 × 214 - a column of three bubbles, 8 apart1

Using the Bubble

Because the other things the source exposes do not multiply the matrix. Reactions are a layer that every variant already carries, hidden, and revealing one does not move the bubble. Which side a message sits on belongs to the row it is in, not to the bubble. So seven variants covers it, and nothing has to be drawn twice.

A colour the kit did not have. Tinted sits between base.secondary and base.muted, and neither of them was the right value, so a token was added rather than a literal typed in: #E8E8E8 in light, #2E2E2E in dark. It themes with everything else, which a hex would not.

It hangs off the bottom edge with a 3px ring in base.card, so it reads as sitting on top of whatever is behind rather than as another row inside. The bubble stays 66 tall whether the pill is showing or not, which is why Reactions is a hidden layer instead of an axis.

For text that belongs in the thread without being a message. No fill, no padding, no radius, 46 tall against everyone else's 66. In the usage frame it is the timestamp under a failed send, which is exactly the case the source describes as unframed assistant text.

From the bubble inside the group, not from a property on the group. A real thread alternates sides, so a two-value axis on Bubble Group could not express it; each bubble is aligned where it belongs and the group only handles the 8px rhythm between them.

Data display · 7 variants