Components/Message

Message

A chat row: an avatar, a bubble, and an optional name above and status below - sixteen variants where the only thing that ever changes is which of the four parts are present.

Message · Components frameLive in the browser · drawn to match the file
Align
Show Avatar
Show Header
Show Footer
OliviaSounds good - I'll send the draft over this afternoon.Delivered
Start · avatar Yes · header Yes · footer Yes480 × 118 in the file
Align is who is talking

Start is the other person: a base.muted bubble with base.foreground text, left of the column. End is you: base.primary with base.primary-foreground, and the whole row reverses so the avatar lands on the right. One property flips the side, the fill and the ink together.

The avatar rides the bubble, not the row

It aligns to the bottom of the bubble, not the bottom of the message, so a footer must not drag it down with it. The component solves this with a 26px spacer under the avatar - the footer's 16 plus the column's 10 gap - that appears only when the footer does. Hiding the avatar keeps the 32px column, so the text never shifts sideways.

Sixteen, and the arithmetic is simple

Two aligns times three yes/no switches is sixteen variants, and the height only ever takes four values. The bubble is 66; a header adds 26, a footer adds 26. 66, 92, 92, 118, with nothing else in the component free to move.

The second component

A group is a stack with one rule

Message Group is 480 × 266: three Messages in a column, 8 apart, stretched to the same width. The eight is the whole component. It exists so a thread has one place to change its rhythm rather than twenty messages each holding their own margin.

Message Group · Components frameThree messages, 8 apart
OliviaSounds good - I'll send the draft over this afternoon.
Sounds good - I'll send the draft over this afternoon.
Sounds good - I'll send the draft over this afternoon.Delivered
The usage frame

Five things a message has to survive

680 by 1224. A real exchange; two messages from the same person; a file hanging off the bottom of a bubble; a send that failed and the three buttons that offer to fix it; and a reply that is one emoji and nothing else. All five are the same component with different parts turned on.

Section
OliviaAre we still on for the review at four?
Yes - I'll bring the updated numbers.
Perfect. See you then.Delivered · 09:41

The last three are where the component stops being a bubble and starts being a row. The attachment is a real Attachment instance sitting under the bubble, indented into the same 40px column so it reads as part of the message. The actions are three ghost buttons in the footer, flush to the message's own edge, which is why they sit right on an End row. And the emoji drops the bubble altogether - no fill, no 280 width, just the glyph at 32.

Notice what the second message in each pair drops. In the thread it is the avatar, because the speaker changed sides and the side already says who it is. In the consecutive pair it is the avatar and the header both, because nothing changed at all - only the bubble is new. The 40px column stays behind either way, so the second bubble lands exactly under the first.

In the file

Two components, sixteen variants

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

MessageAlign × Show Avatar × Show Header × Show FooterStart · End × Yes · No × Yes · No × Yes · No - 480 wide, 66 to 118 tall16
Message Groupnone480 × 266 - three Messages stacked 8 apart1
Usage framefive sections680 × 1224 - thread · consecutive · attachment · actions · emoji0

Using the Message

Three things at once, which is the point of it being one property. Start is the other person: the row runs left to right, the bubble takes base.muted with base.foreground text. End is you: the row reverses so the avatar lands on the right, and the bubble takes base.primary with base.primary-foreground. You never set the fill and the side separately.

Because a status line under the bubble is not part of the message's shape - it is a note about it. If the avatar sat on the bottom of the row it would drop 26 pixels every time a footer appeared, and a thread would look like the faces were sliding around. The component pins it to the bubble's baseline instead.

One number. It is three Messages in a column 8 apart, and that eight is the entire component. Put the spacing in the group and a thread has one place to change its rhythm; put it on each message and you have twenty places.

When nothing has changed. The usage frame shows both cases: in a back-and-forth the reply drops the avatar because the side already says who is speaking, and in two consecutive messages from one person the second drops the avatar and the name both. Only the bubble is new, so only the bubble appears.

The bubble is a real instance of the Bubble component, reactions and all - the Message adds the avatar column, the header and the footer around it. The reaction pill ships hidden in all sixteen, so turning one on is a property change rather than an edit. The usage frame's attachment example brings in a real Attachment instance too, so a file in a chat is the same file card the rest of the kit uses.

Three things, and the usage frame shows each. An attachment sits under the bubble, indented into the same 40px column so it reads as part of the message rather than a new one. Actions - copy, retry, and a thumbs-down - sit in the footer, flush to the message's own edge, which puts them on the right for an End row. And an emoji reply drops the bubble entirely: no fill, no 280 width, just the glyph at 32.

Data display · 16 variants