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.

Table / Cell · Variant × State × Last CellLive in the browser · drawn to match the file
Variant
State
Last Cell
Table Cell Text
190 × 36 in the filedrawn locally1px base.border below
Nine of eleven are borrowed

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.

Eleven variants, seven heights

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.

Last Cell is a divider switch

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.

The head

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.

Table / Head · Variant × StateLive in the browser · drawn to match the file
Variant
State
Head Text
185 × 4014/500 base.foregroundno fill
The assembled table

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.

TableHover a row - the whole row lights, not one cell
InvoiceStatusMethodAmountOwner
INV001PaidCredit Card$120.00Ana
INV002PendingPayPal$340.00Marko
INV003PaidCredit Card$110.00Iva
INV004UnpaidBank Transfer$240.00Petar
INV005PaidPayPal$330.00Ana
Total$1,710.00
A list of your recent invoices.
845 × 292 in the file5 columns at 169 · head 40 · rows 36no row hovered

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.

The usage frame

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.

Table
InvoiceStatusMethodAmountOwner
INV001PaidCredit Card$120.00Ana Kovac
INV002PendingPayPal$340.00Marko Blaz
INV003PaidCredit Card$110.00Iva Sunic
INV004UnpaidBank Transfer$240.00Petar Tomic
INV005PaidPayPal$330.00Ana Kovac
Total$1,710.00
A list of your recent invoices.
845 × 292 in the file

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.

Right to left

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.

dir=rtlLive in the browser · drawn to match the file
Direction
InvoiceStatusAmount
INV001Paid$120.00
INV002Pending$340.00
INV003Paid$110.00
first column on the left · row order unchanged

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.

In the file

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.

Table / CellVariant × State × Last CellDefault · Badge · Avatar · Switch · Button · Dropdown · Progress · Image · Input · Toggle Group · Checkbox × Default · Hover × No · Yes44
Table / HeadVariant × StateDefault · Button × Default · Hover - 185 × 404
Tablenone845 × 292 - five columns, a footer and a caption1

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.

Data display · 48 variants