Detent web components
By Shane Logsdon

Data & tables

Expandable Data Row

Implemented

Table row that unfolds into account details

detent-expandable-data-row

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-data
Group
Data & tables

8.1 KB gzip for the class module

Variants

A local sample table of three accounts. Opening a row unfolds its account metadata, recent activity and quick actions directly beneath the row — no modal. The first row has no metadata, so the documented Standard/Unknown/Card/Recently fallbacks appear. Resize the window below 48em to see the same DOM laid out as labelled cards. Copy Email writes to your clipboard; nothing is fetched and nothing is charged.

No row action has run yet.

Recorded states

  • loading recorded state; no setter
  • expanded recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/expandable-data-row/define.js';

// or register it yourself
import { DetentExpandableDataRow } from '/components/expandable-data-row/expandable-data-row.js';

Attributes

  • allow-multiple
  • is-loading

Properties

  • defaultExpandedIds
  • items
  • onRowAction

Slots

No slots are recorded for this component.

Events

  • detent-row-action — {}

Example

Runnable example: site/examples/expandable-data-row.ts. The snippet below is generated from the catalogued attributes.

<detent-expandable-data-row
  allow-multiple
  is-loading
></detent-expandable-data-row>

Property-only values

const element = document.querySelector('detent-expandable-data-row');
if (element !== null) {
  // items: DataRowItem[]
  element.items = [];
  // defaultExpandedIds: string[]
  element.defaultExpandedIds = [];
  // onRowAction: (action: string, row: DataRowItem) => void
  element.onRowAction = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/ExpandableDataRow.tsx sha256 465ae93bd12d…

Native files

The native module and its artifacts:

  • src/components/expandable-data-row/expandable-data-row.ts
  • src/components/expandable-data-row/styles.css
  • src/components/expandable-data-row/define.ts
  • docs/components/expandable-data-row.md
  • site/examples/expandable-data-row.ts
  • tests/browser/expandable-data-row.spec.ts
  • evidence/components/expandable-data-row.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
allow-multipleallowMultiplebooleanfalsePresence semantics: present means true, absent means false.
is-loadingisLoadingbooleanfalsePresence semantics: present means true, absent means false.

Properties

Properties
PropertyTypeDefaultNotes
defaultExpandedIdsstring[]['usr_01']Property-only: assign from script; not accepted as an attribute.
itemsDataRowItem[][...]Property-only: assign from script; not accepted as an attribute.
onRowAction(action: string, row: DataRowItem) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-row-actiononRowAction(action: string, row: DataRowItem) => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • items defaults to [] and defaultExpandedIds to []. Upstream defaults to a four-row demo dataset and ['usr_01']; baking demo data into the element would fabricate consumer records, so an empty items renders the documented empty state instead.
  • onRowAction becomes the composed detent-row-action event carrying a plain row snapshot; property writes emit none (PC-6.5). It is not available as a property.
  • Copy Email keeps upstream's clipboard write and 2 s Copied feedback but is best-effort: the clipboard API needs a permission grant, so the state is optimistic and the failure is swallowed rather than thrown.
  • Upstream used animate-pulse on hard-coded #0E0E0E/#141414/#1F1F1F surfaces; the port uses theme tokens so both colour schemes exist and meet AA.
  • The mobile layout is a CSS-only conversion of the same table DOM rather than a second, parallel card tree. The visible result (stacked card with label/value pairs) matches; the accessible structure is a table at every width.
  • lucide-react icons (ChevronRight, Copy, Check, CreditCard, ArrowUpRight) become local aria-hidden text glyphs and the Check/Copy swap becomes the Copied label.
  • The springGentle (170/26/0.9) height/opacity presence runs on the shared spring helper: a folding row is aria-expanded=false and inert at once and hidden when the spring settles; defaultExpandedIds seeds paint with no motion. The chevron turns 90 degrees on springSnappy through the shared spring helper.

What it does

All rows in this documentation are local sample data. Nothing is fetched, no billing is performed and the copy action only writes to the clipboard.

Upstream behaviour
  • Soft accordion expansion unfolding details directly beneath rows without modals
  • Single or multi-row simultaneous expansion modes
  • Full metadata breakdown with account metrics and historical activity timeline
  • Adaptive layout engine transforming desktop table into touch cards on mobile devices
  • Integrated quick actions with one-click email copying and callback hooks
Differences from upstream
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • items defaults to [] and defaultExpandedIds to []. Upstream defaults to a four-row demo dataset and ['usr_01']; baking demo data into the element would fabricate consumer records, so an empty items renders the documented empty state instead.
  • onRowAction becomes the composed detent-row-action event carrying a plain row snapshot; property writes emit none (PC-6.5). It is not available as a property.
  • Copy Email keeps upstream's clipboard write and 2 s Copied feedback but is best-effort: the clipboard API needs a permission grant, so the state is optimistic and the failure is swallowed rather than thrown.
  • Upstream used animate-pulse on hard-coded #0E0E0E/#141414/#1F1F1F surfaces; the port uses theme tokens so both colour schemes exist and meet AA.
  • The mobile layout is a CSS-only conversion of the same table DOM rather than a second, parallel card tree. The visible result (stacked card with label/value pairs) matches; the accessible structure is a table at every width.
  • lucide-react icons (ChevronRight, Copy, Check, CreditCard, ArrowUpRight) become local aria-hidden text glyphs and the Check/Copy swap becomes the Copied label.
  • The springGentle (170/26/0.9) height/opacity presence runs on the shared spring helper: a folding row is aria-expanded=false and inert at once and hidden when the spring settles; defaultExpandedIds seeds paint with no motion. The chevron turns 90 degrees on springSnappy through the shared spring helper.
Previous: Hanging Amulet Next: Expandable Search

Docs

Components

Project