Table
Nine of the eleven cell variants are real instances of other components, and the whole table is assembled column by column because Penpot has no grid.
Badge, Avatar, Switch, Button, Icon Button, Progress, Input, Toggle Group and Checkbox are all real instances inside the cell. Only Default and Image are drawn locally - a line of text and a placeholder rectangle. Change Badge and every Badge cell in every table follows.
The cell hugs what is in it: 20 for a progress bar, 32 for a checkbox, 36 for text, 56 for an avatar. That is the number to plan a row on, because a row is as tall as its tallest cell and mixing a Progress cell with an Avatar one gives you 56, not 20.
Every cell carries a 1px base.border along its bottom edge, and Last Cell hides it. That is the axis that stops a table from closing with a rule that has nothing under it - the same rule the Separator page argues for, enforced here as a property.
Forty tall, and sortable or not
185 by 40, with the label at 14/500 in base.foreground - the same weight as an active row, not a muted grey. Variant Button adds a sort arrow beside it; State Hover fills with base.muted the way a body row does, which is what tells you the whole column is clickable.
Built out of columns, not rows
845 by 292: a row of five 169-wide Columns, each one a head with five cells under it, then a footer and a caption. That is backwards from how HTML thinks about a table, and it is the right call in Penpot - a flex row of columns keeps every cell in a column the same width without a grid, which Penpot does not have.
The one thing to watch when you place it: hover is a per-cell State in the file, so a row highlight means setting five cells at once. In code it belongs on the row and costs nothing, and that is the difference the frame cannot show - drawing it cell by cell is a limitation of the medium, not an instruction.
Two tables, same parts
1005 by 954, holding two 845-wide tables. Invoices ends with a total row; products carries an actions column. Both are the same five columns of the same head and cell components, which is the point of the frame - nothing in either is detached.
One thing the file cannot do, and this page can: the menu those action buttons open. A board in Penpot clips its children, so a panel reaching past the edge of the table has nowhere to go, and the Dropdown cell variant stops at the icon button. Everything above it - the five columns, the footer row, the caption - is exactly what the usage frame holds.
Columns reverse, rows do not
The first column belongs at the reading edge, so under dir=rtl it moves to the right and the rest follow leftwards. Row order is unaffected - top is still first. Numbers keep their own direction, which is why an amount column reads the same either way and still needs its alignment set deliberately.
One detail no other component in the kit has to deal with: a currency figure is a left-to-right run inside a right-to-left row. Left to itself the browser will put the dollar sign on the wrong side of the number, which is why the amount above carries its own dir - and why a table is the place bidirectional text actually bites.
Three components, 49 variants
Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.
Using the Table
Because Penpot has no grid. A flex row of five Columns, each holding a head over five cells, keeps every cell in a column the same width without one - and that is the only way to guarantee alignment. It is backwards from how HTML thinks about a table, where a row is the unit, but it is the right call in the medium.
Nine of eleven. Badge, Avatar, Switch, Button, Icon Button, Progress, Input, Toggle Group and Checkbox are real instances inside the cell, so changing any of those components updates every table that uses it. Only Default and Image are local - a line of text and a placeholder rectangle.
Seven of them: 20 for Progress, 32 for Checkbox, 34 for Switch, 36 for text and Badge, 44 for Button, 48 for Dropdown, Input and Toggle Group, 56 for Avatar and Image. A row is as tall as its tallest cell, so a Progress cell next to an Avatar one gives you 56 rather than 20 - worth checking before you mix them.
Hiding the divider. Every cell draws a 1px base.border along its bottom edge, and the last one in a column has to not - otherwise the table closes with a rule that has nothing under it. It is the Separator page's rule about never bounding a list, enforced here as a property rather than left to the person placing it.
In the file it does not, quite. Hover is a State on the cell, so a row highlight means setting five cells at once - and if you miss one, you get a striped row. In code it belongs on the row and costs nothing. That is a limitation of the medium rather than an instruction; the assembled table on this page hovers by row, which is what a reader means.
In the file, under the footer at the bottom of the table. The source's docs describe TableCaption as sitting above the header, and shadcn's own class on it is mt-4, which only makes sense below. Worth deciding once for your build rather than per table - the kit has picked the bottom.