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