# layout.table

Records with the same fields, compared across: members, invoices, API keys. Write plain thead, tbody, tr, th scope="col" and td inside, with no classes; data-off on a tr greys out a row that is switched off. data-head="bar" sets the header row on a bar and pads the outer columns, for a table that runs edge to edge in a panel with no padding. With more columns than fit on a phone, wrap it in a layout.scroller. data-arrival makes its rows rise into place; give each tr a growing animationDelay to stagger them

Component: [`<Layout>`](https://www.prongs.dev/docs/components/layout.md) · Renders: `<table>` · Profile: [UI](https://www.prongs.dev/docs/intents/ui.md)

## Example

```tsx
<Layout intent="layout.table">
  <thead>
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Role</th>
    </tr>
  </thead>
  <tbody>
    {members.map((m) => (
      <tr key={m.id}>
        <td>{m.name}</td>
        <td>{m.role}</td>
      </tr>
    ))}
  </tbody>
</Layout>
```

## Choosing between neighbours

Find the row that describes your element, and use the intent on the right.

| If it is for… | Use |
|---|---|
| Records with the same fields, compared across: members, invoices, API keys. Write plain thead, tbody, tr, th scope="col" and td inside, with no classes; data-off on a tr greys out a row that is switched off. data-head="bar" sets the header row on a bar and pads the outer columns, for a table that runs edge to edge in a panel with no padding. With more columns than fit on a phone, wrap it in a layout.scroller. data-arrival makes its rows rise into place; give each tr a growing animationDelay to stagger them | `layout.table` (this page) |
| Items read one after another, like a feed or notifications | [`layout.list`](https://www.prongs.dev/docs/intents/layout/list.md) |
| Records compared by the same fields, like a members table (rather than [`layout.list`](https://www.prongs.dev/docs/intents/layout/list.md)) | `layout.table` (this page) |
| A real table of data (rather than [`layout.index`](https://www.prongs.dev/docs/intents/layout/index.md)) | `layout.table` (this page) |

## Connections

Declared in the registry, both directions. Where an intent is used in your own code is something check and the gallery see; this site has no app code.

- Built from: [`arrange.table`](https://www.prongs.dev/docs/intents/ui.md#arrange-table)

## For registry authors

**Starter intents.** These are the intents `prongs add` puts in your registry, shown in this site’s theme. Yours are your own `registry.ts`: rename, restyle and add to them, and see them with `prongs add gallery`.

What this intent is built from and its entry in `components/prongs/registry.ts`. You only need this to change how it looks or to add an intent like it.

How layout.table resolves, and its definition

Each layer adds to the one before it; a later class wins over an earlier one that sets the same thing.

- [`arrange.table`](https://www.prongs.dev/docs/intents/ui.md#arrange-table) — Table cells padded and aligned, header cells quiet, a hairline between rows

  ```
  w-full border-collapse text-sm [&_th]:px-3 [&_th]:py-2
  [&_th]:text-left [&_th]:font-medium [&_th]:text-surface-muted
  [&_td]:px-3 [&_td]:py-2 [&_td]:align-middle [&_tr]:border-b
  [&_tr]:border-surface-border [&_tbody_tr:last-child]:border-0
  [&_tbody_tr[data-off]]:opacity-50
  [&_tbody_tr:has(.row-open)]:relative
  [&_tbody_tr:has(.row-open)]:cursor-pointer
  [&_tbody_tr:has(.row-open)]:transition-colors
  [&_tbody_tr:has(.row-open):hover]:bg-surface-hover
  [&_tbody_tr:has(.row-open:focus-visible)]:outline-2
  [&_tbody_tr:has(.row-open:focus-visible)]:-outline-offset-2
  [&_tbody_tr:has(.row-open:focus-visible)]:outline-ring
  [&_tbody_tr:has(.row-open)_:is(button,a):not(.row-open)]:relative
  [&_tbody_tr:has(.row-open)_:is(button,a):not(.row-open)]:z-10
  ```

- `layout.table` (this page) — Records with the same fields, compared across: members, invoices, API keys. Write plain thead, tbody, tr, th scope="col" and td inside, with no classes; data-off on a tr greys out a row that is switched off. data-head="bar" sets the header row on a bar and pads the outer columns, for a table that runs edge to edge in a panel with no padding. With more columns than fit on a phone, wrap it in a layout.scroller. data-arrival makes its rows rise into place; give each tr a growing animationDelay to stagger them

  ```
  data-[arrival]:[&_tbody_tr]:animate-[showcase-in_0.5s_ease-out_both]
  motion-reduce:data-[arrival]:[&_tbody_tr]:animate-none
  data-[head=bar]:[&_thead_tr]:border-0
  data-[head=bar]:[&_thead_th]:bg-surface-hover
  data-[head=bar]:[&_thead_th]:py-2.5
  data-[head=bar]:[&_:is(th,td):first-child]:pl-5
  data-[head=bar]:[&_:is(th,td):last-child]:pr-5
  data-[head=bar]:[&_tbody_tr:last-child]:border-b
  ```

Final classes

```
w-full border-collapse text-sm [&_th]:px-3 [&_th]:py-2
[&_th]:text-left [&_th]:font-medium [&_th]:text-surface-muted
[&_td]:px-3 [&_td]:py-2 [&_td]:align-middle [&_tr]:border-b
[&_tr]:border-surface-border [&_tbody_tr:last-child]:border-0
[&_tbody_tr[data-off]]:opacity-50
[&_tbody_tr:has(.row-open)]:relative
[&_tbody_tr:has(.row-open)]:cursor-pointer
[&_tbody_tr:has(.row-open)]:transition-colors
[&_tbody_tr:has(.row-open):hover]:bg-surface-hover
[&_tbody_tr:has(.row-open:focus-visible)]:outline-2
[&_tbody_tr:has(.row-open:focus-visible)]:-outline-offset-2
[&_tbody_tr:has(.row-open:focus-visible)]:outline-ring
[&_tbody_tr:has(.row-open)_:is(button,a):not(.row-open)]:relative
[&_tbody_tr:has(.row-open)_:is(button,a):not(.row-open)]:z-10
data-[arrival]:[&_tbody_tr]:animate-[showcase-in_0.5s_ease-out_both]
motion-reduce:data-[arrival]:[&_tbody_tr]:animate-none
data-[head=bar]:[&_thead_tr]:border-0
data-[head=bar]:[&_thead_th]:bg-surface-hover
data-[head=bar]:[&_thead_th]:py-2.5
data-[head=bar]:[&_:is(th,td):first-child]:pl-5
data-[head=bar]:[&_:is(th,td):last-child]:pr-5
data-[head=bar]:[&_tbody_tr:last-child]:border-b
```

Definition

```ts
'layout.table': {
  kind: 'semantic',
  description: 'Records with the same fields, compared across: members, invoices, API keys. Write plain thead, tbody, tr, th scope="col" and td inside, with no classes; data-off on a tr greys out a row that is switched off. data-head="bar" sets the header row on a bar and pads the outer columns, for a table that runs edge to edge in a panel with no padding. With more columns than fit on a phone, wrap it in a layout.scroller. data-arrival makes its rows rise into place; give each tr a growing animationDelay to stagger them',
  refs: ['arrange.table'],
  binding: {
    element: 'table',
    classes: 'data-[arrival]:[&_tbody_tr]:animate-[showcase-in_0.5s_ease-out_both] motion-reduce:data-[arrival]:[&_tbody_tr]:animate-none data-[head=bar]:[&_thead_tr]:border-0 data-[head=bar]:[&_thead_th]:bg-surface-hover data-[head=bar]:[&_thead_th]:py-2.5 data-[head=bar]:[&_:is(th,td):first-child]:pl-5 data-[head=bar]:[&_:is(th,td):last-child]:pr-5 data-[head=bar]:[&_tbody_tr:last-child]:border-b',
  },
  notFor: [
    {
      context: 'items read one after another, like a feed or notifications',
      instead: 'layout.list',
    },
  ],
},
```
