---
url: "https://vueda.dev/v3/reference/theming/keys/ObjectsGrid.md"
title: "ObjectsGrid"
---

# ObjectsGrid

Source: `client/lib/theme/vueda-tailwind/objects-grid/index.js`

> The outer scroll surface and card fill for the grid. It owns the density attribute, tabular number rendering, and `data-flush` edge merging used when the grid is embedded in a parent surface. The radius and inset hairline apply in table layout only, where the root is the surface the rows sit on and its edge closes the last row. Card layout drops the edge, because each card already carries one through [ObjectsGrid.bodyRow](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-bodyRow) and an edge here would sit 4 px outside every card edge. The fill stays in both layouts, so the gutter between cards keeps the same tone as the chrome a consumer wraps around the grid. `data-flush` removes the embedded grid's inset hairline and radius; the parent owns the outer edge and any separators around the grid.

Vue component: [ObjectsGrid](/v3/reference/api/vue/ObjectsGrid.md)

| Slot | Default classes | Description |
| --- | --- | --- |
| [root](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-root) | callback | The outer scroll surface and card fill for the grid. It owns the density attribute, tabular number rendering, and `data-flush` edge merging used when the grid is embedded in a parent surface. The radius and inset hairline apply in table layout only, where the root is the surface the rows sit on and its edge closes the last row. Card layout drops the edge, because each card already carries one through [ObjectsGrid.bodyRow](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-bodyRow) and an edge here would sit 4 px outside every card edge. The fill stays in both layouts, so the gutter between cards keeps the same tone as the chrome a consumer wraps around the grid. `data-flush` removes the embedded grid's inset hairline and radius; the parent owns the outer edge and any separators around the grid. |
| [table](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-table) | callback | The inner table-shaped container. It switches on the table display contract only when the active breakpoint resolves to table layout, and fills the root so row dividers reach both card edges instead of stopping at the last column. The separated border model keeps cell padding free of collapsed edges, so row dividers sit on cells rather than on rows. |
| [headerRowGroup](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-headerRowGroup) | callback | The header row group for table layout. It is hidden in card layout because card labels come from [ObjectsGridCardCell.header](/v3/reference/theming/keys/ObjectsGridCardCell.md#theme-key-ObjectsGridCardCell-header). |
| [headerRow](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-headerRow) | callback | The table-layout header row wrapper. It becomes a table row only when [ObjectsGrid.table](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-table) is in table mode, and carries the divider that separates the header band from the first data row. The divider sits on the header cells because the separated border model does not paint borders on rows. |
| [headerCell](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-headerCell) | callback | The table-layout column header cell. It aligns numeric columns with `data-numeric` so sortable columns keep their right edge stable. |
| [emptyText](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-emptyText) | `text-center` | The empty-state cell wrapper. It centers the empty content inside the single row the grid renders when it has no data. That row spans the full width in both layouts, but by different means: in table layout the cell is a `td` carrying `colspan`, because a CSS table gives a non-cell child one column; in card layout it is a `div` inside a row that spans the card grid. A recipe here should therefore set no display or column utilities of its own. |
| [emptyContent](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-emptyContent) | 4 classes | The empty-state content stack. It styles the icon, title, and variant-driven loading or error treatment used by the default empty slot. |
| [bodyRowGroup](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-bodyRowGroup) | callback | The body row group for loading, empty, and data rows. It is a responsive card grid in card layout and a table row group in table layout. In table layout it removes the last row's divider from that row's cells so the enclosing [ObjectsGrid.root](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-root) edge remains the closing edge. |
| [bodyRow](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-bodyRow) | callback | The shared row wrapper for table rows and card rows. It carries hover, selected, and marked-destroy states while preserving action controls. Selected rows use a low-primary tint plus a leading primary rail so selection stays distinct from hover. In table layout each row also divides from the next; that divider sits on the row's cells, because the separated border model does not paint borders on rows. In card layout the row is the card: it carries the card fill, radius, and edge, so it reads the same on any ground and is the only edge in that layout. |
| [cardContainer](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-cardContainer) | 3 classes | The card-layout field grid inside each row card. It aligns all [ObjectsGridCardCell.header](/v3/reference/theming/keys/ObjectsGridCardCell.md#theme-key-ObjectsGridCardCell-header) and [ObjectsGridCardCell.value](/v3/reference/theming/keys/ObjectsGridCardCell.md#theme-key-ObjectsGridCardCell-value) fragments into label and value columns. |
| [rowActions](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-rowActions) |  | Row-action classes exposed for consumers that compose inline row controls next to object rows. Use `rowActions.root` for the hidden wrapper and `rowActions.action` for compact icon buttons. |

<a id="theme-key-ObjectsGrid-root"></a>

## root

> The outer scroll surface and card fill for the grid. It owns the density attribute, tabular number rendering, and `data-flush` edge merging used when the grid is embedded in a parent surface. The radius and inset hairline apply in table layout only, where the root is the surface the rows sit on and its edge closes the last row. Card layout drops the edge, because each card already carries one through [ObjectsGrid.bodyRow](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-bodyRow) and an edge here would sit 4 px outside every card edge. The fill stays in both layouts, so the gutter between cards keeps the same tone as the chrome a consumer wraps around the grid. `data-flush` removes the embedded grid's inset hairline and radius; the parent owns the outer edge and any separators around the grid.

Callback:

```javascript
({ isTable }) => [
                "max-w-full overflow-x-auto",
                "bg-card text-foreground text-body",
                "[font-variant-numeric:tabular-nums_slashed-zero]",
                {
                    "rounded-vueda-card hairline hairline-border": isTable,
                },
                // Flush variant: an ancestor stamps `data-flush="true"` (e.g. FieldSetTabularInline.body)
                // to merge the grid into a parent card without doubling borders. Drop the rounded edge
                // and inset hairline; the parent owns the outer edge and feedback-panel separator.
                "[[data-flush]_&]:rounded-none [[data-flush]_&]:shadow-none",
            ]
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:18`

<a id="theme-key-ObjectsGrid-table"></a>

## table

> The inner table-shaped container. It switches on the table display contract only when the active breakpoint resolves to table layout, and fills the root so row dividers reach both card edges instead of stopping at the last column. The separated border model keeps cell padding free of collapsed edges, so row dividers sit on cells rather than on rows.

Callback:

```javascript
({ isTable }) => [
                {
                    "!table w-full border-separate border-spacing-0": isTable,
                },
            ]
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:33`

<a id="theme-key-ObjectsGrid-headerRowGroup"></a>

## headerRowGroup

> The header row group for table layout. It is hidden in card layout because card labels come from [ObjectsGridCardCell.header](/v3/reference/theming/keys/ObjectsGridCardCell.md#theme-key-ObjectsGridCardCell-header).

Callback:

```javascript
({ isTable }) => [
                "gap-1 2xs:gap-2 2xl:gap-4",
                {
                    "!table-header-group": isTable,
                    hidden: !isTable,
                },
            ]
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:41`

<a id="theme-key-ObjectsGrid-headerRow"></a>

## headerRow

> The table-layout header row wrapper. It becomes a table row only when [ObjectsGrid.table](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-table) is in table mode, and carries the divider that separates the header band from the first data row. The divider sits on the header cells because the separated border model does not paint borders on rows.

Callback:

```javascript
({ isTable }) => [{ "!table-row": isTable, "[&>*]:border-b-hairline": isTable }]
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:51`

<a id="theme-key-ObjectsGrid-headerCell"></a>

## headerCell

> The table-layout column header cell. It aligns numeric columns with `data-numeric` so sortable columns keep their right edge stable.

Callback:

```javascript
({ isTable }) => [
                "align-bottom",
                "font-semibold",
                "select-none",
                // Match the body cells and TableHead: a label wider than its column's values (a
                // "Unit Price" header over "0.38") widens the column instead of wrapping.
                "whitespace-nowrap",
                "data-[numeric]:text-right",
                {
                    "!table-cell": isTable,
                },
            ]
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:55`

<a id="theme-key-ObjectsGrid-emptyText"></a>

## emptyText

> The empty-state cell wrapper. It centers the empty content inside the single row the grid renders when it has no data. That row spans the full width in both layouts, but by different means: in table layout the cell is a `td` carrying `colspan`, because a CSS table gives a non-cell child one column; in card layout it is a `div` inside a row that spans the card grid. A recipe here should therefore set no display or column utilities of its own.

Default classes:

`text-center`

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:70`

<a id="theme-key-ObjectsGrid-emptyContent"></a>

## emptyContent

> The empty-state content stack. It styles the icon, title, and variant-driven loading or error treatment used by the default empty slot.

Default classes:

```text
flex flex-col items-center justify-center gap-2.5 py-10 text-center text-muted-foreground
[&>[data-slot=icon]]:text-xl [&>[data-slot=icon]]:text-muted-foreground/70
data-[variant=loading]:[&>[data-slot=icon]]:animate-spin
data-[variant=error]:[&>[data-slot=icon]]:text-destructive/80
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:74`

<a id="theme-key-ObjectsGrid-bodyRowGroup"></a>

## bodyRowGroup

> The body row group for loading, empty, and data rows. It is a responsive card grid in card layout and a table row group in table layout. In table layout it removes the last row's divider from that row's cells so the enclosing [ObjectsGrid.root](/v3/reference/theming/keys/ObjectsGrid.md#theme-key-ObjectsGrid-root) edge remains the closing edge.

Callback:

```javascript
({ isTable }) => [
                "print:block",
                {
                    // 1 / 2 / 3 column responsive default; projects override via theme as needed.
                    "grid grid-flow-row grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 p-1 2xs:p-2 2xl:p-4 gap-1 2xs:gap-2 2xl:gap-4":
                        !isTable,
                    "!table-row-group [&>*:last-child>*]:border-b-0": isTable,
                },
            ]
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:84`

<a id="theme-key-ObjectsGrid-bodyRow"></a>

## bodyRow

> The shared row wrapper for table rows and card rows. It carries hover, selected, and marked-destroy states while preserving action controls. Selected rows use a low-primary tint plus a leading primary rail so selection stays distinct from hover. In table layout each row also divides from the next; that divider sits on the row's cells, because the separated border model does not paint borders on rows. In card layout the row is the card: it carries the card fill, radius, and edge, so it reads the same on any ground and is the only edge in that layout.

Callback:

```javascript
({ isTable }) => {
                return [
                    "group/row transition-colors",
                    "hover:bg-accent/50 active:bg-accent",

                    // Selected row state.
                    "data-[state=selected]:bg-primary/[0.06]",
                    "data-[state=selected]:hover:bg-primary/[0.09]",
                    "data-[state=selected]:active:bg-primary/[0.12]",
                    "data-[state=selected]:[box-shadow:inset_2px_0_0_0_var(--primary)]",

                    // marked-destroy: 4% destructive tint and a strikethrough on every cell whose
                    // `data-field` / `data-card` / `data-card-header` is not the action column. Action
                    // controls are excluded so the user can still click "undo" on the destroy mark.
                    "data-[state=marked-destroy]:bg-destructive/[0.04]",
                    "data-[state=marked-destroy]:[&_[data-field]:not([data-field=item-action-bar])]:line-through",
                    "data-[state=marked-destroy]:[&_[data-card]:not([data-card=item-action-bar])]:line-through",
                    "data-[state=marked-destroy]:[&_[data-card-header]:not([data-card-header=item-action-bar])]:line-through",
                    // you can't tell how many cards are on a row, so we must treat them all the same.
                    // first and last don't help us here.
                    {
                        "!table-row [&>*]:border-b-hairline": isTable,
                        "p-1 2xs:p-2 2xl:p-4 rounded-vueda-card hairline hairline-border bg-card overflow-y-auto":
                            !isTable,
                    },
                ];
            }
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:96`

<a id="theme-key-ObjectsGrid-cardContainer"></a>

## cardContainer

> The card-layout field grid inside each row card. It aligns all [ObjectsGridCardCell.header](/v3/reference/theming/keys/ObjectsGridCardCell.md#theme-key-ObjectsGridCardCell-header) and [ObjectsGridCardCell.value](/v3/reference/theming/keys/ObjectsGridCardCell.md#theme-key-ObjectsGridCardCell-value) fragments into label and value columns.

Default classes:

```text
p-1 2xs:p-2 2xl:p-4 gap-1 2xs:gap-2 2xl:gap-4 mb-1 mt-2
grid grid-cols-[minmax(96px,max-content)_1fr] items-baseline
[&>*]:min-w-0
```

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:126`

<a id="theme-key-ObjectsGrid-rowActions"></a>

## rowActions

> Row-action classes exposed for consumers that compose inline row controls next to object rows. Use `rowActions.root` for the hidden wrapper and `rowActions.action` for compact icon buttons.

Source: `client/lib/theme/vueda-tailwind/objects-grid/ObjectsGrid.theme.js:139`
