List
The generic slotted list-row primitive — shared chrome for list grids whose cells are bespoke. It owns the desktop column header, the rows container, and the clickable row card with its mobile-stacked vs desktop-grid switch; every cell stays page-authored. Where list table + list card render the fixed quote/transaction columns, this family can represent any team, clients, organisations, or branches row shape.
Canonical
@{ const string DesktopGrid = "grid grid-cols-[minmax(200px,1.5fr)_minmax(220px,2fr)_minmax(120px,1fr)] gap-4 items-center"; }
<koala-list-header grid-class="@DesktopGrid">
<div>Name</div>
<div>Email</div>
<div>Role</div>
</koala-list-header>
<koala-list-rows rows-id="team-rows">
@foreach (var member in Model.Team)
{
<koala-list-row href="@ViewUser.Route(member.Id)" grid-class="@DesktopGrid">
<koala-list-mobile>...stacked cells...</koala-list-mobile>
<koala-list-desktop>...one <div> per header column...</koala-list-desktop>
</koala-list-row>
}
</koala-list-rows>
The most-used shape — a team list. Resize the window to see each row flip between a stacked card and a horizontal grid at sm. The page keeps a single const string DesktopGrid and passes it to the header and every row, so the columns always line up and Tailwind compiles the literal.
Reach for this family when the row cells are bespoke — team, clients, organisations, users, releases, branches. Quote and transaction lists have a fixed column set and use list table + list card instead.
Variants
3 variants<koala-list-row delegate grid-class="@DesktopGrid">
<koala-list-desktop>
<div class="min-w-0 flex items-center gap-2">
<a data-primary href="@ViewUser.Route(member.Id)" x-target.push="main"
class="font-semibold text-on-surface hover:underline truncate">@member.DisplayName</a>
</div>
...
<div class="flex justify-end">
<button type="button" koala-icon-button="Neutral" koala-icon-button-variant="Ghost" class="w-8 h-8">
<koala-icon name="MoreVertical" />
</button>
</div>
</koala-list-desktop>
</koala-list-row>
States
1 stateDelegate rows add a keyboard state: the <div> is focusable (tabindex="0"), Enter follows the a[data-primary] link, and Cmd/Ctrl-click opens it in a new tab.
Props
8 attributes| Element | Attribute | Notes |
|---|---|---|
| koala-list-header | grid-class | Full grid class string for the desktop header row — the same literal grid grid-cols-[…] … each row uses, so header and rows line up. Authored in the .cshtml so Tailwind compiles it. |
| koala-list-header | breakpoint | Sm (default) or Lg. Breakpoint at which the header appears (hidden below). |
| koala-list-rows | rows-id | Optional id on the rows container. Set when the page uses Alpine-AJAX x-merge="append" for load-more batches — point koala-load-more at the same id. The container always carries the space-y-3 gap. |
| koala-list-row | href | Destination the row navigates to (swaps #main via x-target.push). Ignored when delegate is set. |
| koala-list-row | grid-class | The shared desktop grid literal — pass the same string as the header. |
| koala-list-row | breakpoint | Sm (default) or Lg. Where the row flips from stacked to grid. Both slots read it from the row, so set it once per row (matching the header). |
| koala-list-row | delegate | Render a click-delegating <div> instead of an <a>, for rows with nested interactive controls. Navigation comes from the row's inner a[data-primary] link. |
| koala-list-mobile | class | Extra classes merged after the visibility class (e.g. flex items-center gap-3). |
| koala-list-desktop | — | No attributes. Reads the breakpoint and grid class from the parent <koala-list-row> and renders hidden {bp}:grid {grid-class}. |
Do & don't
<div class="sm:hidden">...</div>
<div class="hidden sm:grid ...">...</div>
</a>
Mobile & desktop slots
Every <koala-list-row> authors its cells twice — that's the
dual-render contract. <koala-list-mobile> is the stacked layout,
hidden at/above the row's breakpoint; <koala-list-desktop> is
hidden below it and flips to a grid using the row's grid-class.
Both slots read the breakpoint and grid spec from the parent row, so they carry no configuration of their
own — the desktop slot just needs one <div> per column of
the shared grid, in header order.
<koala-list-row href="@viewUrl" grid-class="@DesktopGrid">
@* Below the breakpoint — stacked. Extra classes merge after the visibility class. *@
<koala-list-mobile>
<div class="flex items-center gap-2 min-w-0">...</div>
<div class="mt-1 text-on-surface-muted truncate">...</div>
</koala-list-mobile>
@* At/above the breakpoint — one <div> per column of the shared grid. *@
<koala-list-desktop>
<div>...</div>
<div>...</div>
<div>...</div>
</koala-list-desktop>
</koala-list-row>
The two slots exist so each layout can be honest about its shape — mobile stacks identity, detail, and badges vertically; desktop places one cell per column. Don't try to force one markup tree to serve both with utility gymnastics; author each slot for its layout.