# Tables & Feeds (/islands/tables-and-feeds)





Three islands for rows you read one at a time: a **table** when exact values matter, a
**feed** for things that happened in order, and a **search box** to find one row in many.
Each is a real island below: bind it to fields in your data and the runtime renders it. See
the [islands overview](/islands/overview.md) for the full registry.

## `table.grid` [#tablegrid]

**What it is.** A paginated table of raw rows. &#x2A;*When to use it:** when the exact numbers
matter and a chart would hide them: positions, transactions, line items. It needs only a
`dataset`; with no `columns` it shows every column in the data.

<LiveIsland
  type="table.grid"
  config="{
  title: &#x22;Positions&#x22;,
  columns: [
    { field: &#x22;ticker&#x22;, label: &#x22;Ticker&#x22; },
    { field: &#x22;shares&#x22;, label: &#x22;Shares&#x22;, format: &#x22;int&#x22; },
    { field: &#x22;value_eur&#x22;, label: &#x22;Value&#x22;, format: &#x22;eur&#x22; },
  ],
}"
  data="sampleData(
  [
    { name: &#x22;ticker&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;shares&#x22;, type: &#x22;bigint&#x22; },
    { name: &#x22;value_eur&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { ticker: &#x22;VWCE&#x22;, shares: 312, value_eur: 38_400 },
    { ticker: &#x22;AAPL&#x22;, shares: 80, value_eur: 16_120 },
    { ticker: &#x22;MSFT&#x22;, shares: 45, value_eur: 19_850 },
    { ticker: &#x22;NVDA&#x22;, shares: 30, value_eur: 33_900 },
    { ticker: &#x22;BRK.B&#x22;, shares: 22, value_eur: 9_240 },
  ],
  &#x22;positions&#x22;,
)"
  height="300"
/>

```jsonc title="manifest.json"
{
  "type": "table.grid",
  "title": "Positions",
  "dataset": "positions",
  "columns": [
    { "field": "ticker", "label": "Ticker" },
    { "field": "shares", "label": "Shares", "format": "int" },
    { "field": "value_eur", "label": "Value", "format": "eur" }
  ]
}
```

**Key options.**

* `columns: [{ field, label?, format? }]`: pick and order the columns and set each column's
  [value format](/reference/value-formats.md). Omit it to show every column.
* `details: [{ field, label?, format? }]`: fields *hidden* from the row and revealed in a
  click-to-open dialog, each with its own optional [value format](/reference/value-formats.md).
  Declaring `details` makes each row clickable.
* `groupBy: { field, titleField?, subtitleField? }`: render rows as collapsible sections by
  `field`; the section title and subtitle read from each group's first row.
* `drilldown: { island, match }`: embed another island in the row-details dialog, its rows
  filtered by `match` (a map of *drilldown-dataset column* → *clicked-row field*). One level
  deep: a drilldown island has no drilldown of its own.
* `expand: false`: drop the see-all dialog and render every row inline (default `true`).

<Callout type="info" title="Tip">
  `details` and `drilldown` only do something once a row is clickable. A row becomes clickable
  the moment you declare `details` (or a `drilldown`), so a table that's just for scanning needs
  neither.
</Callout>

## `timeline.feed` [#timelinefeed]

**What it is.** A reverse-chronological feed of rows. &#x2A;*When to use it:** logs, activity
streams, a journal, anything that reads as "what happened, newest first." It needs a `ts`
(the timestamp) and a `titleField`; the runtime sorts so the newest row is on top and renders
each timestamp smartly (a date-only value as `Jun 11, 2026`, a real timestamp as
`Jun 11, 21:30`).

<LiveIsland
  type="timeline.feed"
  config="{
  title: &#x22;Activity&#x22;,
  ts: &#x22;ts&#x22;,
  titleField: &#x22;action&#x22;,
  detail: &#x22;actor&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;ts&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;action&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;actor&#x22;, type: &#x22;varchar&#x22; },
  ],
  [
    { ts: &#x22;2026-06-09 09:14&#x22;, action: &#x22;Bought 30 NVDA&#x22;, actor: &#x22;rebalance&#x22; },
    { ts: &#x22;2026-06-10 11:02&#x22;, action: &#x22;Dividend received&#x22;, actor: &#x22;VWCE&#x22; },
    { ts: &#x22;2026-06-11 16:48&#x22;, action: &#x22;Sold 12 AAPL&#x22;, actor: &#x22;manual&#x22; },
    { ts: &#x22;2026-06-12 08:30&#x22;, action: &#x22;Rebalanced portfolio&#x22;, actor: &#x22;rule&#x22; },
    { ts: &#x22;2026-06-13 10:05&#x22;, action: &#x22;Deposited €2,000&#x22;, actor: &#x22;transfer&#x22; },
  ],
  &#x22;activity&#x22;,
)"
  height="300"
/>

```jsonc title="manifest.json"
{
  "type": "timeline.feed",
  "title": "Activity",
  "dataset": "activity",
  "ts": "ts",
  "titleField": "action",
  "detail": "actor"
}
```

**Key options.**

* `detail`: a secondary string shown next to the title. `kind`: a category string.
* `details` / `drilldown` / `expand`: exactly as on `table.grid` (click-to-open dialog,
  embedded filtered island, render-all toggle).
* `groupBy: { field, titleField?, subtitleField? }`: collapsible sections, same shape as on
  the table.
* **Rich rows.** Setting any of `highlight`, `stats`, or `footer` switches a row from a single
  line to a header/stats/footer layout:

  * `highlight: { field, format?, unit? }`: an emphasized value pinned to the right of the title.
  * `stats: [{ field, label?, format?, unit?, color? }]`: labeled inline stats under the title;
    labels default to a palette, `color` pins one.
  * `footer: [{ field, label?, format?, unit?, pill? }]`: a meta line led by the timestamp;
    `pill: true` renders the value as a badge.

  Each of these takes an optional [value format](/reference/value-formats.md) (`format`) or
  free-form `unit` to style its value.

```jsonc title="manifest.json"
{
  "type": "timeline.feed",
  "title": "Workouts",
  "dataset": "workouts",
  "ts": "ts",
  "titleField": "name",
  "highlight": { "field": "strain", "format": "int" },
  "stats": [
    { "field": "avg_hr", "label": "HR", "unit": "bpm" },
    { "field": "kcal", "label": "Cal", "unit": "kcal" }
  ],
  "footer": [{ "field": "sport", "pill": true }]
}
```

## `search.box` [#searchbox]

**What it is.** A client-side search input over a dataset. &#x2A;*When to use it:** the dataset is
large enough that you'd rather look a row up than scroll. Typing matches rows case-insensitively
across `fields`; results drop down as an autocomplete showing `titleField`, and selecting one
opens that row's full details.

<LiveIsland
  type="search.box"
  config="{
  fields: [&#x22;name&#x22;, &#x22;category&#x22;],
  titleField: &#x22;name&#x22;,
  detail: &#x22;category&#x22;,
  placeholder: &#x22;Search foods…&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;name&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;category&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;kcal&#x22;, type: &#x22;bigint&#x22; },
  ],
  [
    { name: &#x22;Greek yogurt&#x22;, category: &#x22;Dairy&#x22;, kcal: 59 },
    { name: &#x22;Rolled oats&#x22;, category: &#x22;Grains&#x22;, kcal: 389 },
    { name: &#x22;Banana&#x22;, category: &#x22;Fruit&#x22;, kcal: 89 },
    { name: &#x22;Chicken breast&#x22;, category: &#x22;Protein&#x22;, kcal: 165 },
    { name: &#x22;Almonds&#x22;, category: &#x22;Nuts&#x22;, kcal: 579 },
    { name: &#x22;Spinach&#x22;, category: &#x22;Vegetable&#x22;, kcal: 23 },
  ],
  &#x22;ingredients&#x22;,
)"
  height="140"
/>

```jsonc title="manifest.json"
{
  "type": "search.box",
  "dataset": "ingredients",
  "fields": ["name", "category"],
  "titleField": "name",
  "detail": "category",
  "placeholder": "Search foods…"
}
```

**Key options.**

* `fields: [...]`: the columns a query matches against (substring, case-insensitive).
* `titleField`: what each result row shows; `detail`: an optional secondary line under it.
* `placeholder?`: the input's placeholder text.
* `limit?`: the maximum number of visible results (default `10`).

<Callout type="info" title="Note">
  `search.box` matches with a plain client-side substring over the rows already loaded; it isn't
  a SQL `LIKE` and doesn't page through the whole dataset. For a huge table, shape a narrower
  `sql` dataset for it to search.
</Callout>


---

*This is one page of the OpenIslands docs. Every page in one file: [/llms-full.txt](/llms-full.txt). Page index: [/llms.txt](/llms.txt). Links above point to `.md` siblings — append `.md` to any page URL for its raw markdown.*
