Intentwise Primitives

Tables

Tabular data display powered by TanStack Table with built-in sorting, pagination, and specialized cell renderers for currency, percent, sparkline, and trend values.

AdvancedAnalyticsTable

Campaign Analytics Table

Enterprise-grade DSP table with virtualization, row selection, column resizing, sortable headers, and a totals strip. All state is fully controlled.

Show code
<AdvancedAnalyticsTable
  data={rows}
  columns={createAdvancedAnalyticsColumns({ enableRowSelection: true })}
  getRowId={(row) => row.id}
  sorting={sorting} onSortingChange={setSorting}
  columnVisibility={visibility} onColumnVisibilityChange={setVisibility}
  columnSizing={sizing} onColumnSizingChange={setSizing}
  pagination={pagination} onPaginationChange={setPagination}
  rowSelection={selection} onRowSelectionChange={setSelection}
  maxHeight={420}
  enablePagination showTotalsRow
  selectedRowId={selectedRowId} onSelectedRowIdChange={setSelectedRowId}
  onCellCommit={handleCellCommit}
/>

Pinned Columns

Pass columnPinning={{ left: [...] }} to freeze columns. Pinned cells have a solid background so they stay readable during horizontal scroll. Maximum 3 columns per side.

Show code
<AdvancedAnalyticsTable
  columnPinning={{
    left: [
      ADVANCED_ANALYTICS_COLUMN_IDS.select,
      ADVANCED_ANALYTICS_COLUMN_IDS.deliveryActivationStatus,
      ADVANCED_ANALYTICS_COLUMN_IDS.orderName,
    ],
  }}
  // ... other props
/>

Compare Mode

Toggle compare mode to see each metric expand into Current / Previous / Change / Change% sub-columns. Click the chevron on a group header to expand or collapse it.

Show code
const columns = createCompareAnalyticsColumns({
  enableRowSelection: true,
  expandedGroups: new Set(['spend', 'impressions']),
});
const rows = buildCompareRows(baseRows);

<AdvancedAnalyticsTable
  data={rows}
  columns={columns}
  onHeaderGroupAction={({ groupId }) => toggleGroup(groupId)}
/>

EntityNameCell β€” copy to clipboard

Pass enableCopy to show a hover-revealed copy icon next to the name. onCopy fires after a successful clipboard write β€” use it to raise a toast. copyValue overrides the clipboard text (e.g. an ID instead of the display name).

Hover over a name to reveal the copy icon:
Sponsored Products β€” Brand Awareness
Auto-targeting β€” Competitor Keywords
Manual Targeting β€” High ROAS
Show code
<EntityNameCell
  value="Sponsored Products β€” Brand Awareness"
  entityId="camp_001"
  enableCopy
  onCopy={(text) => toast('success', `Copied: ${text}`)}
/>

// With a separate clipboard value
<EntityNameCell
  value="Sponsored Products β€” Brand Awareness"
  entityId="camp_001"
  enableCopy
  copyValue="camp_001"
  onCopy={(text) => toast('success', `ID copied: ${text}`)}
/>

Full-Featured Table

Sortable columns, paginated, with inline SparkLine and TrendBadge cell renderers.

Show code
<DataTable data={tabularData} title="Top Customers" striped />

Compact Density

Use density='compact' for tighter rows in data-dense layouts.

Show code
<DataTable data={data} density="compact" />

Table with Total

Compact embedded table pattern with totals and fixed-height scrolling.

Show code
<DataTable
  data={ordersData}
  density="compact"
  maxHeight="480px"
  hoverable
  showTotalsRow
  totalsLabelColumnKey="orderName"
  embedded
/>

Inline Editable Currency Cell

Small reusable inline-edit control used by DSP Orders current budget cells.

Show code
<InlineEditableCurrencyCell
  value={3000}
  currencySymbol="$"
  min={0.01}
  onSubmit={async (nextValue) => save(nextValue)}
/>

Inline Table Input Cells

Always-visible controlled input cells for editable table rows. InlineTextInputCell for text, InlineNumberInputCell for numeric values with optional prefix and stepper, and InlineDatePickerCell for date/time selection with min/max constraints.

Show code
<InlineTextInputCell value={name} onChange={setName} />

<InlineNumberInputCell value={budget} onChange={setBudget} min={0} prefix="$" step={100} />

<InlineDatePickerCell value={startDate} onChange={setStartDate} min={todayIso} />

Basic Table (No Extras)

Minimal table without sorting, pagination, or specialized renderers.

Show code
<DataTable data={basicData} />

ComparisonTable

Campaign Comparison

Side-by-side comparison of metrics across columns. Shows delta percentages vs a baseline column and highlights the best value per row.

Campaign A vs Campaign B

Performance comparison with baseline deltas

Metric
Campaign A(base)
Campaign B
Revenue
$12.5K
$15.8K+26.4%
ROAS
3.20
4.10+28.1%
ACoS
31.3%
24.4%-22.0%
Impressions
450K
520K+15.6%
Clicks
8,500
11,200+31.8%
CTR
1.9%
2.1%+13.8%
Show code
<ComparisonTable
  data={{ columns: [...], rows: [...], showDelta: true, highlightBest: true }}
  title="Campaign A vs B"
/>

Without Deltas

Hide the percentage change column for a simpler view.

Simple Comparison

Metric
Campaign A(base)
Campaign B
Revenue
$12.5K
$15.8K
ROAS
3.20
4.10
ACoS
31.3%
24.4%
Impressions
450K
520K
Clicks
8,500
11,200
CTR
1.9%
2.1%
Show code
<ComparisonTable data={{ ...data, showDelta: false }} />

Size Variants

Available in sm, md (default), and lg sizes.

sm

Metric
Campaign A(base)
Campaign B
Revenue
$12.5K
$15.8K+26.4%
ROAS
3.20
4.10+28.1%
ACoS
31.3%
24.4%-22.0%
Impressions
450K
520K+15.6%
Clicks
8,500
11,200+31.8%
CTR
1.9%
2.1%+13.8%

md

Metric
Campaign A(base)
Campaign B
Revenue
$12.5K
$15.8K+26.4%
ROAS
3.20
4.10+28.1%
ACoS
31.3%
24.4%-22.0%
Impressions
450K
520K+15.6%
Clicks
8,500
11,200+31.8%
CTR
1.9%
2.1%+13.8%

lg

Metric
Campaign A(base)
Campaign B
Revenue
$12.5K
$15.8K+26.4%
ROAS
3.20
4.10+28.1%
ACoS
31.3%
24.4%-22.0%
Impressions
450K
520K+15.6%
Clicks
8,500
11,200+31.8%
CTR
1.9%
2.1%+13.8%
Show code
<ComparisonTable data={...} size="sm" />

TimelineView

Campaign Timeline

Vertical timeline showing sequential events with status indicators, dates, and descriptions.

Campaign History

Key milestones and events

πŸš€
Campaign CreatedJan 15, 2024

Initial campaign setup with 50 keywords

Budget IncreasedFeb 1, 2024

Daily budget raised from $50 to $100

Performance DipFeb 15, 2024

CTR dropped below 1.5% threshold

Paused for ReviewMar 1, 2024

Campaign paused pending bid optimization

ReactivatedMar 10, 2024

Keywords optimized, negative keywords added

Record Sales DayMar 25, 2024

$2,500 in attributed sales

Show code
<TimelineView
  data={{ events: [...] }}
  title="Campaign History"
/>

Without Dates

Hide date labels for a cleaner look.

Events Only

πŸš€
Campaign Created

Initial campaign setup with 50 keywords

Budget Increased

Daily budget raised from $50 to $100

Performance Dip

CTR dropped below 1.5% threshold

Paused for Review

Campaign paused pending bid optimization

Reactivated

Keywords optimized, negative keywords added

Record Sales Day

$2,500 in attributed sales

Show code
<TimelineView data={...} showDates={false} />

DailyPerformanceTable

Daily metrics table

Table with date columns and metric rows; supports WoW and optional groups.

Daily Performance (Feb 01 - Feb 07)

METRICS(WoW)
FEB 01SAT
FEB 02SUN
FEB 03MON
FEB 04TUE
FEB 05WED
FEB 06THU
FEB 07FRI
Total Sales(+2.0%)
$12.2k$11.8k$13.1k$12.5k$14.0k$12.9k$13.5k
Ad Spend(-1.5%)
$2.1k$2.0k$2.3k$2.2k$2.4k$2.1k$2.2k
Show code
<DailyPerformanceTable
  dateRangeLabel="Feb 01 - Feb 07"
  dates={['2025-02-01', ...]}
  rows={[{ label: 'Total Sales', wowValue: '+2.0%', cells: [{ value: '$12.2k' }, ...] }, ...]}
/>

EightWeekSalesTrend

8-week sales trend

Up to 8 weeks with value, WoW %, and mix %. Optional performance label (Outperformed/Underperformed).

8-Week Total Sales Trend

Jan 05 - Jan 11
$45.2K
WoW2.1%
Mix0.5%
Outperformed
Jan 12 - Jan 18
$48.1K
WoW1.2%
Mix0.3%
Underperformed
Jan 19 - Jan 25
$46.9K
WoW1.8%
Mix0.2%
Show code
<EightWeekSalesTrend
  title="8-Week Total Sales Trend"
  weeks={[{ dateRange: 'Jan 05 - Jan 11', value: 45200, wowPercent: 2.1, mixPercent: 0.5 }, ...]}
/>

TableColumnSelector

Column visibility and ordering

Modal selector for showing, hiding, pinning, and reordering table columns.

{
  "columns": [
    {
      "colId": "name",
      "hide": false,
      "width": 0
    },
    {
      "colId": "revenue",
      "hide": false,
      "width": 0
    },
    {
      "colId": "spend",
      "hide": false,
      "width": 0
    },
    {
      "colId": "roas",
      "hide": true,
      "width": 0
    }
  ],
  "pinnedIds": []
}
Show code
<TableColumnSelector
  columns={columns}
  open={open}
  onOpenChange={setOpen}
  onApply={(state) => setState(state)}
  initialState={state}
/>

Cell Renderers

TextCell

Basic text cell with null-coalescing placeholder.

With valueCampaign Alpha
Null value–
Custom placeholderN/A
With transformHELLO WORLD
Show code
<TextCell value="Hello" />
<TextCell value={null} placeholder="N/A" />
const renderer = createTextCellRenderer({ placeholder: 'N/A' });

FormattedTextCell

Text cell with built-in format transforms: humanize-enum, title-case, uppercase, lowercase.

humanize-enumActive Campaign
title-caseHello World
uppercaseHELLO
lowercasehello
Show code
<FormattedTextCell value="ACTIVE_CAMPAIGN" format="humanize-enum" />
<FormattedTextCell value="hello world" format="title-case" />

TotalsAwareTextCell

Applies bold styling when the row is a totals row.

Normal rowOrders
Totals rowTotal
Show code
<TotalsAwareTextCell value="Orders" row={{ _isTotal: false }} />
<TotalsAwareTextCell value="Total" row={{ _isTotal: true }} />

StatusResultCell

Colored outcome indicator β€” green for success, red for failure, muted for unknown.

SuccessSUCCESS
FailureFAILED
UnknownPENDING
Custom labelsApproved
Show code
<StatusResultCell value="SUCCESS" />
<StatusResultCell value="FAILED" />
<StatusResultCell value="PENDING" />

Cell Renderer Registry

createCellRendererRegistry() returns a map of factory functions for use with table column definitions.

registry.textCampaign A
registry.humanizeActive Status
registry.titleCaseHello World
registry.statusSUCCESS
Show code
const registry = createCellRendererRegistry();
// registry.text('Campaign A', {})
// registry.humanize('ACTIVE_STATUS', {})
// registry.status('SUCCESS', {})

SelectableTable

Interactive Selection

Table with checkbox column, header select-all, and sortable columns.

CampaignStatusSpendROAS
Brand Defence – SPActive$1,240.504.2x
Category Conquest – SBActive$890.303.1x
Retargeting – SDPaused$320.002.8x
New Launch – SPActive$2,100.755.6x
Competitor ASIN – SPEnded$450.201.9x

0 of 5 selected

Show code
<SelectableTable columns={cols} rows={rows} rowIdField="id"
  selectedIds={selectedIds} onSelectionChange={setSelectedIds} />

Radio (single-select)

selectionMode='radio' replaces checkboxes with radio buttons β€” selecting a row deselects all others.

CampaignStatusSpendROAS
Brand Defence – SPActive$1,240.504.2x
Category Conquest – SBActive$890.303.1x
Retargeting – SDPaused$320.002.8x
New Launch – SPActive$2,100.755.6x
Competitor ASIN – SPEnded$450.201.9x

Selected: none

Show code
<SelectableTable selectionMode="radio" columns={cols} rows={rows} rowIdField="id"
  selectedIds={selectedIds} onSelectionChange={setSelectedIds} />

Row Height Constraints

rowMinHeight and rowMaxHeight set a floor/ceiling on each row. Useful when cells contain variable-height content like tags or wrapped text.

CampaignStatusSpendROAS
Brand Defence – SPActive$1,240.504.2x
Category Conquest – SBActive$890.303.1x
Retargeting – SDPaused$320.002.8x
New Launch – SPActive$2,100.755.6x
Competitor ASIN – SPEnded$450.201.9x
Show code
<SelectableTable rowMinHeight="48px" rowMaxHeight="72px"
  columns={cols} rows={rows} rowIdField="id"
  selectedIds={selectedIds} onSelectionChange={setSelectedIds} />

Loading State

Shows a loading overlay while data is being fetched.

CampaignStatusSpendROAS
Loading...
Show code
<SelectableTable loading columns={cols} rows={[]} rowIdField="id"
  selectedIds={new Set()} onSelectionChange={() => {}} />

SimpleTable

Basic Display Table

Read-only table for presenting tabular data without selection or sorting. Columns support custom renderers, fixed widths, text alignment, and cellClassName for td-level styling.

CampaignSpendROASStatus
SP - Auto - Core Products$4,8203.2xEnabled
SB - Brand - Video$2,1502.8xPaused
SP - Manual - Competitor$1,3904.1xEnabled
Show code
const columns: SimpleTableColumn<Row>[] = [
  { key: 'campaign', header: 'Campaign' },
  { key: 'spend', header: 'Spend', align: 'right', cellClassName: 'tabular-nums', render: (row) => `$${row.spend}` },
  { key: 'status', header: 'Status', render: (row) => <StatusBadge status={row.status} /> },
];

<SimpleTable columns={columns} rows={rows} rowIdField="id" />

Results Table (no rowIdField)

When rowIdField is omitted the array index is used as the React key β€” safe for static, read-only results lists such as post-API success/failure detail tables.

KeywordMatch TypeUpdated Status
running shoesexactSUCCESS
blue sneakersbroadFAILED: bid below minimum
trail runnersphraseSUCCESS
Show code
const columns: SimpleTableColumn<ResultRow>[] = [
  { key: 'keyword', header: 'Keyword' },
  { key: 'matchType', header: 'Match Type', cellClassName: 'capitalize' },
  {
    key: 'updatedStatus',
    header: 'Updated Status',
    cellClassName: 'font-medium',
    render: (row) => (
      <span className={row.success ? 'text-iw-success' : 'text-iw-error'}>{row.updatedStatus}</span>
    ),
  },
];

<SimpleTable columns={columns} rows={results} />

Empty State

Renders a centered message when the rows array is empty. Customise via emptyMessage.

CampaignSpendROASStatus
No campaigns found.
Show code
<SimpleTable columns={columns} rows={[]} emptyMessage="No campaigns found." />