Skip to content
On this page Wide and stacked

Layout

RecordList

Records as dense rows (identity, a few columns, a row menu) that re-flow to a compact two-line row on a phone.

<script>
  import { RecordList, RecordRow, RecordCell } from "@codeatlasdev/ui";
</script>

<RecordList columns="minmax(0,1.2fr) minmax(0,1.6fr) 5rem 2.75rem" labels={["App", "Scopes", "Installs"]} aria-label="Apps">
  {#each apps as app (app.id)}
    <RecordRow>
      {#snippet identity()}{app.name}{/snippet}
      <RecordCell label="Scopes">{app.scopes}</RecordCell>
      <RecordCell label="Installs">{app.installs}</RecordCell>
      {#snippet actions()}<DropdownMenu>…</DropdownMenu>{/snippet}
    </RecordRow>
  {/each}
</RecordList>

The usual dashboard list is neither a table nor a card per record. Table with responsive="cards" makes each row three or four times taller on a phone, and a Card per record repeats the chrome. RecordList owns what every app hand-rolls around that shape: the flush container, the separators, one column template shared by the header and every row, the re-flow below a width, and a 44px actions cell.

columns is one grid-template-columns value: the identity first, one track per RecordCell, the actions track last. labels are the column headers of the identity and the cells. A row without actions simply leaves the last track out of columns.

Wide and stacked

Wide, the header is an aria-hidden row and every RecordCell keeps its label as a visually hidden dt, so assistive technology still gets “Scopes: read:invoices” per cell. Below 36rem of the list’s own width (a container query, not the viewport) the header disappears and the row becomes two lines: identity and actions on the first, the cells below as small label/value pairs that wrap. The same list behaves the same in a narrow card or beside a sidebar, and it needs no JavaScript, so server-rendered pages are correct from the first paint.

With no rows, render your own empty state instead of the list.

#245

Apps

Identity with avatar, status chip and a row menu. Narrow the window or the container to see it stack.

  • BI Billing
    Scopes
    read:invoices write:invoices
    Installs
    128
    Status
    Live
    Created
    Mar 14, 2026
  • SD Support desk
    Scopes
    read:tickets
    Installs
    7
    Status
    Live
    Created
    Apr 2, 2026
  • AE Analytics exporter
    Scopes
    read:events read:users
    Installs
    0
    Status
    Draft
    Created
    May 21, 2026

Reference #245 · Pick in the gallery

Code
svelte
<script lang="ts">
  import {
    Avatar, DropdownMenu, DropdownMenuItem, IconButton, RecordCell, RecordList, RecordRow, StatusChip,
  } from '@codeatlasdev/ui';
  import { MoreHorizontal } from '@lucide/svelte';

  const apps = [
    { id: 1, name: 'Billing', scopes: 'read:invoices write:invoices', installs: 128, created: 'Mar 14, 2026', live: true },
    { id: 2, name: 'Support desk', scopes: 'read:tickets', installs: 7, created: 'Apr 2, 2026', live: true },
    { id: 3, name: 'Analytics exporter', scopes: 'read:events read:users', installs: 0, created: 'May 21, 2026', live: false },
  ];
</script>

<RecordList
  columns="minmax(0,1.3fr) minmax(0,1.8fr) 4.5rem 5.5rem 6.5rem 2.75rem"
  labels={['App', 'Scopes', 'Installs', 'Status', 'Created']}
  aria-label="Apps"
>
  {#each apps as app (app.id)}
    <RecordRow>
      {#snippet identity()}
        <span class="who"><Avatar alt={app.name} size="sm" /> <strong>{app.name}</strong></span>
      {/snippet}
      <RecordCell label="Scopes"><code>{app.scopes}</code></RecordCell>
      <RecordCell label="Installs">{app.installs}</RecordCell>
      <RecordCell label="Status">
        <StatusChip status={app.live ? 'success' : 'neutral'} label={app.live ? 'Live' : 'Draft'} />
      </RecordCell>
      <RecordCell label="Created">{app.created}</RecordCell>
      {#snippet actions()}
        <DropdownMenu align="end">
          {#snippet triggerChild(props)}
            <IconButton aria-label={`Actions for ${app.name}`} {...props}><MoreHorizontal size={18} /></IconButton>
          {/snippet}
          <DropdownMenuItem>Edit</DropdownMenuItem>
          <DropdownMenuItem>Rotate secret</DropdownMenuItem>
        </DropdownMenu>
      {/snippet}
    </RecordRow>
  {/each}
</RecordList>

<style>
  .who { display: flex; align-items: center; gap: var(--ca-space-2); min-width: 0; }
  code { font-size: var(--ca-font-size-caption); }
</style>
#247

Phone width

A 344px slot: the same list stacked, because the list reads its own width.

  • PK Production key
    Prefix
    ca_live_9f2…
    Last used
    2 min ago
    Created
    Jan 8, 2026
  • CP CI pipeline
    Prefix
    ca_live_41c…
    Last used
    Yesterday
    Created
    Feb 19, 2026

Reference #247 · Pick in the gallery

Code
svelte
<script lang="ts">
  import {
    Avatar, DropdownMenu, DropdownMenuItem, IconButton, RecordCell, RecordList, RecordRow,
  } from '@codeatlasdev/ui';
  import { MoreHorizontal } from '@lucide/svelte';

  const keys = [
    { id: 1, name: 'Production key', prefix: 'ca_live_9f2…', used: '2 min ago', created: 'Jan 8, 2026' },
    { id: 2, name: 'CI pipeline', prefix: 'ca_live_41c…', used: 'Yesterday', created: 'Feb 19, 2026' },
  ];
</script>

<!-- The list reacts to its own width, so a 344px slot (a 390px phone minus page padding) stacks
     exactly like the real thing, whatever the browser window is. -->
<div class="slot">
  <RecordList
    columns="minmax(0,1.3fr) minmax(0,1fr) 7rem 7rem 2.75rem"
    labels={['Key', 'Prefix', 'Last used', 'Created']}
    aria-label="API keys"
  >
    {#each keys as key (key.id)}
      <RecordRow>
        {#snippet identity()}
          <span class="who"><Avatar alt={key.name} size="sm" /> <strong>{key.name}</strong></span>
        {/snippet}
        <RecordCell label="Prefix"><code>{key.prefix}</code></RecordCell>
        <RecordCell label="Last used">{key.used}</RecordCell>
        <RecordCell label="Created">{key.created}</RecordCell>
        {#snippet actions()}
          <DropdownMenu align="end">
            {#snippet triggerChild(props)}
              <IconButton aria-label={`Actions for ${key.name}`} {...props}><MoreHorizontal size={18} /></IconButton>
            {/snippet}
            <DropdownMenuItem>Revoke</DropdownMenuItem>
          </DropdownMenu>
        {/snippet}
      </RecordRow>
    {/each}
  </RecordList>
</div>

<style>
  .slot { inline-size: min(100%, 21.5rem); }
  .who { display: flex; align-items: center; gap: var(--ca-space-2); min-width: 0; }
  code { font-size: var(--ca-font-size-caption); }
</style>
#246

Without a menu

No actions slot and no actions track: three columns that stack the same way.

  • https://example.com/hooks/orders
    Events
    order.created
    Delivered
    99.8%
  • https://example.com/hooks/refunds
    Events
    refund.created refund.failed
    Delivered
    100%

Reference #246 · Pick in the gallery

Code
svelte
<script lang="ts">
  import { RecordCell, RecordList, RecordRow } from '@codeatlasdev/ui';

  const hooks = [
    { id: 1, url: 'https://example.com/hooks/orders', events: 'order.created', ok: '99.8%' },
    { id: 2, url: 'https://example.com/hooks/refunds', events: 'refund.created refund.failed', ok: '100%' },
  ];
</script>

<RecordList
  columns="minmax(0,2fr) minmax(0,1.4fr) 5rem"
  labels={['Endpoint', 'Events', 'Delivered']}
  aria-label="Webhooks"
>
  {#each hooks as hook (hook.id)}
    <RecordRow>
      {#snippet identity()}<code>{hook.url}</code>{/snippet}
      <RecordCell label="Events">{hook.events}</RecordCell>
      <RecordCell label="Delivered">{hook.ok}</RecordCell>
    </RecordRow>
  {/each}
</RecordList>

<style>
  code { font-size: var(--ca-font-size-caption); overflow-wrap: anywhere; }
</style>

Accessibility

The list is a real <ul> with one <li> per record: give it an aria-label. Per-cell labels are dt/dd pairs inside a dl, so they are announced with their value in both layouts. Put an aria-label that names the record on every action button (“Actions for Billing”), because the visible column header is hidden from assistive technology.

RecordList props

PropTypeDefaultDescription
columnsstring–`grid-template-columns` shared by the header and every row: identity, one track per cell, then actions.
labelsstring[]–Column headers for the identity and each cell, in order. Omit for no header row.
classstring''Extra class names on the wrapper.
childrenSnippet–The RecordRows.

RecordRow props

PropTypeDefaultDescription
identitySnippet–First column: what the record is. Heads the stacked row.
actionsSnippet–Last column, usually a DropdownMenu. At least 44px square.
classstring''Extra class names on the row.
childrenSnippet–The RecordCells, in the order of the `columns` tracks.

RecordCell props

PropTypeDefaultDescription
labelstring–The column name: kept for assistive technology on wide rows, painted above the value when stacked.
classstring''Extra class names on the cell.
childrenSnippet–The value.