Record detail
A record's Details tab, read-only, for a standard or custom object. Same field descriptions as Record form and Record inline edit.
Installation
The CLI installs the Radix or Base UI file to match your project, with what it builds on: expandable-section, lookup, output-field.
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- A record page's Details tab: the fields of one record in the page layout's sections, the way Lightning's record detail shows an opportunity, an account or a custom object's record.
- A pencil on each field opens an edit elsewhere, like a modal with Record form.
onEditsays which field was pressed. - The same field descriptions serve Record form and Record inline edit, so one set covers view, edit and inline edit.
Don't use it
- One field, or your own arrangement of fields. Use Output field.
- People edit the fields in place, with Cancel and Save at the bottom. Use Record inline edit.
- People create or edit the record in a form. Use Record form.
- Many records side by side. Use Data table.
Usage
import {
RecordDetail,
RecordDetailField,
RecordDetailSection,
} from "@/components/wisty/record-detail"RecordDetail
└── RecordDetailSection
└── RecordDetailFieldfields describes every field, keyed by API name: its label, type and help, with editable: false for a field people can't change and href for a lookup's link. A description also takes the Output field format keys its type reads: scale, minimumFractionDigits and maximumFractionDigits on a number, plus currencyDisplay on a currency, dateStyle on a date, the date and time styles, hour12 and timeZoneName on a datetime, and linkLabel, linkTarget and linkTitle on an email, phone or URL. Record form and Record inline edit take the same descriptions, with what editing needs added. values is the record. Each RecordDetailField draws one field by name, its value formatted by the field's type.
<RecordDetail
fields={{
Name: { label: "Account Name", type: "text" },
OwnerId: {
label: "Account Owner",
type: "lookup",
href: (record) => `/users/${record.id}`,
editable: false,
},
AnnualRevenue: { label: "Annual Revenue", type: "currency", scale: 0 },
LastModifiedDate: {
label: "Last Modified Date",
type: "relative",
editable: false,
},
}}
values={{
Name: "Edge Communications",
OwnerId: { id: "005Dn00000EllenH", name: "Ellen Hart" },
AnnualRevenue: 139000000,
LastModifiedDate: "2026-10-13T12:00:00Z",
}}
now={now}
onEdit={(name) => openEditForm(name)}
>
<RecordDetailSection title="Account Information">
<RecordDetailField name="Name" />
<RecordDetailField name="OwnerId" />
<RecordDetailField name="AnnualRevenue" />
</RecordDetailSection>
<RecordDetailSection title="System Information" defaultOpen={false}>
<RecordDetailField name="LastModifiedDate" />
</RecordDetailSection>
</RecordDetail>Fields fill each row left to right, then down, as a page layout's rows do. To lay out a page layout loaded at runtime, map its sections to RecordDetailSection and their field names to RecordDetailField, as Record form's Usage does.
Examples
Account with addresses
An account's Billing and Shipping Address on their lines, a missing country left out, and Last Modified Date as `type="relative"` from a fixed `now`.
One column
The same record with `columns={1}` and `layout="horizontal"`, labels beside values, as for a narrow region or compact density.
Loading
With `loading` and no values yet, the labels stay and each value is a placeholder until the record arrives.
Empty fields
A new opportunity whose Amount, Next Step, Type and Lead Source are `null`: they render blank, as an empty field does.
API reference
| Part | Built on | Description |
|---|---|---|
RecordDetail | <div> (opens in a new tab) | A record's fields, read-only, in the page layout's collapsible sections. |
RecordDetailSection | <div> (opens in a new tab) | A collapsible section of the page layout. |
RecordDetailField | <div> (opens in a new tab) | One field by API name: its label and formatted value, with a pencil when onEdit is set. |
RecordDetail
A record's fields, read-only, in the page layout's collapsible sections.
| Prop | Type | Default | Description |
|---|---|---|---|
fields* | RecordDetailFields | Every field the layout can show, keyed by API name. | |
values | RecordDetailValues | {} | The record's values, keyed by API name. A missing or null value shows blank. |
columns | 1 | 2 | 2 | Fields per row. Default 2. Two columns fall to one in a narrow container. |
layout | "auto" | "stacked" | "horizontal" | "auto" | Label above the value, or beside it from a 48rem screen. Default "auto": beside it when the page is compact (data-density="compact"), above it otherwise. |
loading | boolean | false | Holds every value's place while the record loads. Without children, draws placeholder fields. |
headingLevel | 2 | 3 | 4 | 2 | The level of each section's heading. Default 2, under a record page's h1. |
onEdit | (name: string) => void | Called with the field's API name when its pencil is pressed. Without it, no pencils show. | |
locale | string | "en-US" | A BCP 47 locale for numbers and dates. Default "en-US". |
currency | string | "USD" | An ISO 4217 code for currency fields. Default "USD". |
timeZone | string | "UTC" | An IANA time zone for datetime fields. Default "UTC". |
now | string | number | Date | The instant relative fields count from, as ISO 8601. Defaults to the current time at render. |
RecordDetailSection
A collapsible section of the page layout.
| Prop | Type | Default | Description |
|---|---|---|---|
title* | string | The section's heading, as the page layout names it. | |
defaultOpen | boolean | true | Whether the section starts open, uncontrolled. Default true. |
open | boolean | Whether the section is open, controlled. | |
onOpenChange | (open: boolean) => void | Called when the toggle opens or closes the section. | |
columns | 1 | 2 | Fields per row, over the root's columns. |
RecordDetailField
One field by API name: its label and formatted value, with a pencil when onEdit is set.
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | Drawn after the value and its pencil, like a lock on a field people can't edit. | |
name* | string | The field's API name, a key of the root's fields. |
RecordDetailOptiontype
A picklist value and its label.
| Member | Type | Default | Description |
|---|---|---|---|
value* | string | What's stored. | |
label* | string | What's shown. |
RecordDetailFieldDescriptiontype
What a field is and how it reads; Record form's description extends it for editing.
type RecordDetailFieldDescription = RecordDetailFieldBase &
(
| { type: "text" | "textarea" }
| ({ type: "email" | "phone" | "url" } & OutputFieldLinkOptions)
| ({ type: "number" | "percent" } & RecordDetailDigits)
| ({ type: "currency" } & RecordDetailDigits &
Pick<OutputFieldStyleOptions, "currencyDisplay">)
| ({
type: "date"
} & Pick<OutputFieldStyleOptions, "dateStyle">)
| {
type: "picklist"
options: readonly RecordDetailOption[]
}
| { type: "boolean" }
| {
type: "address"
}
| {
type: "name"
}
| {
type: "lookup"
records?: readonly LookupRecord[]
href?: (record: LookupRecord) => string | undefined
}
| ({
type: "datetime"
} & Pick<
OutputFieldStyleOptions,
"dateStyle" | "timeStyle" | "hour12" | "timeZoneName"
>)
| ({
type: "time"
} & Pick<OutputFieldStyleOptions, "timeStyle" | "hour12">)
| ({
type: "relative"
editable: false
} & Pick<OutputFieldStyleOptions, "dateStyle" | "timeStyle" | "hour12">)
)RecordDetailValuestype
The record's values, keyed by API name: a lookup's is its record, a compound field's its parts, an instant ISO 8601.
type RecordDetailValues = Record<
string,
| string
| number
| boolean
| LookupRecord
| OutputFieldAddress
| OutputFieldName
| null
| undefined
>RecordDetailFieldstype
The record's fields, keyed by API name.
type RecordDetailFields = Record<string, RecordDetailFieldDescription>Accessibility
- Each section's title is a heading with the toggle inside it:
h2by default, for a record page whose header is theh1. Under another heading, setheadingLevelone level below it. - Each section's fields are a list, one item per field, and each field is a group named by its label. A pencil is named after its field, like "Edit Amount".
- While loading, the block is busy and every value is a placeholder.
Responsive behavior
- Two columns need about 584px. In a narrower container the fields stack in one column, in the same order.
- Labels follow Salesforce's display density by default (
layout="auto"): above the values, and beside them when the page is compact (data-density="compact"on<html>).layout="stacked"or"horizontal"fixes it for one block. - Beside means from a 48rem screen, whatever the block's own width; below that they stack.
Rules
- Name only fields in
fields; a name that isn't there throws. Leave out a field the user can't read rather than passing it asnull. nullis an empty field and renders blank. Never pass 0, a dash or "None" for it.- A percent is a fraction: divide a Salesforce percent field's value by 100. A date is a day (
"2026-11-20"), a datetime an instant. A lookup's value is its record ({ id, name }). - Pass
scalefrom the field's definition, so whole-dollar fields don't show cents. An explicitminimumFractionDigitsormaximumFractionDigitswins over it on its end. - An
addressis{ street, city, state, postalCode, country }and shows on its lines: the street, then "City, State PostalCode", then the country, a missing part dropping out with its comma. Anameis{ salutation, firstName, middleName, lastName, suffix }on one line. Both follow Salesforce's en-US order for every country and locale. One with no parts renders blank. - A
relativefield must beeditable: false. Adatetimevalue is an instant, shown intimeZone; atimevalue is a time of day as Salesforce returns it ("09:30:00.000Z"), never moved bytimeZone. Arelativevalue is an instant: passnow, one value for the page, so the text stays the same between the server and the browser.
Related
- Record inline editA record's Details tab, edited in place with a pencil, Cancel and Save.
- Record formLightning's New and Edit record form, with required checks and save errors.
- Output fieldShows a field's label and formatted value read-only, with an edit pencil.
- FieldLays out a form control with its label, help text and error.
- Expandable sectionOpens and closes a titled section of a page from its gray title bar.
- Data tableShows records in a table that sorts, selects, runs row actions and loads more.