Attribute / spec key-value grid
What this solves
Information hierarchy and reuse discipline: a record's fields need one predictable label/value shape everywhere they surface, so a reader doesn't relearn the layout moving between the peek panel, the modal, and the full tab. Speedway holds this in one real component; walmart's version has drifted into two identical copies, which is exactly the risk a shared grid closes.
Use it when
- A generated field's value needs the same shape on the peek panel, the modal, and the full tabspeedway ContentFieldsTable: one Table-backed component, three call sites across ProductItem.tsx and part-detail-tabs.tsx
- A record's identity and taxonomy fields need a compact label/value summary, not a tablewalmart-mvp DetailBlock/DetailRow: a dl grid with a mono flag and an italic n/a fallback, defined separately in ItemDetailPanel.tsx and TaxonomyTab.tsx
Rendered
Fixture data; check both themes.Brake pad, front axle
Generated 2 minutes agoIdentity
- SKUsku
- BRK-Q3-40821
- UPCupc
- 038932107744
- Brand
- Raybestos
Specifications
- Material
- Semi-metallic composite with a ceramic-blended friction layer for quieter stops
- Rotor diameter
- 11.65 in
- Bolt pattern
- Required field missing
Marketing content
- Description
- Engineered for daily-driver stopping power without the squeal.Generated by gemini-3.5-pro, run #4821
- Fitment notes
- n/a
patterns/attribute-spec-grid/example.tsxCopy this into a fresh route and it renders as above.
"use client";import { Card, FieldChip, Tooltip } from "@versable-git/ui";// The row vocabulary a record's fields actually need, gathered into one// fixture record. mono + n/a fallback come from walmart-mvp's DetailRow// (ItemDetailPanel.tsx:411-430); the raw field key beside a label is// FieldChip's own documented job (a data field's name, wherever one is// shown); the required-but-missing warning treatment comes from walmart-mvp's// separate AttributesList (asterisk-prefixed keys in warning tone,// ItemDetailPanel.tsx:430-453); "Generated by" echoes speedway's// ContentFieldsTable "By" column, which real usage gates to staff.type AttributeRow = { label: string; fieldKey?: string; value?: string | null; mono?: boolean; tooltip?: string; generatedBy?: string; critical?: boolean;};type AttributeSection = { label: string; rows: AttributeRow[];};const SECTIONS: AttributeSection[] = [ { label: "Identity", rows: [ { label: "SKU", fieldKey: "sku", value: "BRK-Q3-40821", mono: true }, { label: "UPC", fieldKey: "upc", value: "038932107744", mono: true }, { label: "Brand", value: "Raybestos" }, ], }, { label: "Specifications", rows: [ { label: "Material", value: "Semi-metallic composite with a ceramic-blended friction layer for quieter stops", tooltip: "Semi-metallic composite with a ceramic-blended friction layer for quieter stops", }, { label: "Rotor diameter", value: "11.65 in" }, { label: "Bolt pattern", value: null, critical: true }, ], }, { label: "Marketing content", rows: [ { label: "Description", value: "Engineered for daily-driver stopping power without the squeal.", generatedBy: "gemini-3.5-pro, run #4821", }, { label: "Fitment notes", value: null }, ], },];function Row({ row }: { row: AttributeRow }) { const valueClass = ["m-0 text-sm", row.mono && "text-base-content/70 font-mono text-xs"] .filter(Boolean) .join(" "); return ( <> <dt className="text-base-content/65 flex items-center gap-1.5 text-sm"> {row.label} {row.fieldKey && <FieldChip>{row.fieldKey}</FieldChip>} </dt> <dd className={valueClass}> {row.value ? ( row.tooltip ? ( <Tooltip content={row.tooltip} placement="top-start"> <span className="block max-w-[280px] truncate">{row.value}</span> </Tooltip> ) : ( row.value ) ) : ( <span className={row.critical ? "text-warning italic" : "text-base-content/65 italic"}> {row.critical ? "Required field missing" : "n/a"} </span> )} {row.generatedBy && ( <span className="text-base-content/45 mt-0.5 block text-xs">Generated by {row.generatedBy}</span> )} </dd> </> );}function AttributeGrid({ sections }: { sections: AttributeSection[] }) { return ( <div className="flex flex-col gap-4"> {sections.map((section) => ( <div key={section.label} className="flex flex-col gap-1.5"> <span className="text-base-content/65 text-xs font-semibold tracking-wide uppercase"> {section.label} </span> <dl className="m-0 grid grid-cols-[max-content_1fr] gap-x-4 gap-y-2"> {section.rows.map((row) => ( <Row key={row.label} row={row} /> ))} </dl> </div> ))} </div> );}export function AttributeSpecGrid() { return ( <Card title="Brake pad, front axle" subtitle="Generated 2 minutes ago" noAnimate> <AttributeGrid sections={SECTIONS} /> </Card> );}Where it ships
speedway/app/components/content-fields.tsx:172-179ContentFieldsTable, its own docstring: shared by the part page, the peek panel, and the preview modal, so a generated field looks the same wherever someone finds it (called from ProductItem.tsx:458,531 and part-detail-tabs.tsx:233)walmart-mvp/frontend/src/features/catalog/ItemDetailPanel.tsx:400-430DetailBlock/DetailRow, a dl grid with a mono flag; the identical shape is redefined, not imported, at TaxonomyTab.tsx:475-483
App-specific: Speedway's version rides inside a Table (Field, Content, and a staff-gated By/provenance column); walmart's is a plain semantic dl, and its required-attribute warning treatment comes from a separate AttributesList component (asterisk-prefixed keys in warning tone, ItemDetailPanel.tsx:430-453). Neither canon doc names this composite outright; the closest is doc 5 §A8's dl-as-stat-list bullet ('a two-column dl grid for label/value summaries inside a card when a table is too heavy, admin team usage'), which this recipe follows. Real usage also needs the staff gate speedway applies to provenance, and an empty-vs-critical distinction driven by the record's own required-field list, both left out of the fixture.